1 Introduction

This note covers the fundamental concepts of probability distributions and their characterization through various functions, followed by non-parametric estimation methods for these distributions from data.

Probability distributions form the bedrock of statistical analysis, providing the mathematical framework to describe and analyze random phenomena. The complete behavior of any random variable can be understood through four key functions:

  • Cumulative Distribution Function (CDF) captures the probability of observing values up to a certain point;

  • Probability Density Function (PDF) which describes the relative likelihood at specific values for continuous variables;

  • Survival Function measures the probability of exceeding a given value; and

  • Hazard Function quantifies the instantaneous risk of an event occurring.

These interrelated functions provide complementary perspectives on the same underlying distribution, each offering unique insights for different analytical contexts. Understanding these fundamental concepts is crucial not only for theoretical probability but also for practical statistical inference, as they enable us to move from abstract mathematical descriptions to concrete data analysis through estimation techniques like empirical distributions and kernel density estimation.

2 Probability Distribution Functions

This section discusses the four key functions that characterize a general distribution.

2.1 Cumulative Distribution Function (CDF)

The Cumulative Distribution Function (CDF) of a random variable \(X\) is defined as:

\[ F(x)=P(X \le x) \] Properties:

  • \(F(x)\) is non-decreasing

  • \(\lim_{x \to -\infty} F(x) = 0\)

  • \(\lim_{x \to \infty} F(x) = 1\)

  • \(F(x)\) is right-continuous

Example: A graphical representation of the standard normal distribution CDF:

\[ \Phi(x) = F(x) = \int_{-\infty}^x \frac{1}{\sqrt{2\pi}} \exp\left[-\frac{t^2}{2} \right]dt, \ \ -\infty < x < \infty. \]

# Create sequence of x values
x <- seq(-4, 4, length.out = 1000)

# Calculate CDF for normal distribution
cdf.normal <- pnorm(x)      # CDF

# Create data frame for plotting
cdf.df <- data.frame(x = x, CDF = cdf.normal)

# Plot CDF
cdf.plt <- ggplot(cdf.df, aes(x = x, y = CDF)) +
  geom_line(color = "blue", linewidth = 1) +
  geom_hline(yintercept = c(0, 1), linetype = "dashed", alpha = 0.5) +
  labs(title = "Cumulative Distribution Function (CDF) of Standard Normal",
       x = "percentiles of standard normal distribution", y = "Cumulative Distribution: F(x)") +
  theme(plot.title = element_text(hjust = 0.5),
        plot.margin = margin(t = 35, r = 20, b = 30, l = 30, unit = "pt"))
ggplotly(cdf.plt)

3 Probability Density Function (PDF)

For continuous random variables, the Probability Density Function (PDF) is the derivative of the CDF:

\[ f(x)= \frac{dF(x)}{dx} \]

Properties:

  • \(f(x) \geq 0\) for all \(x\)

  • \(\int_{-\infty}^{\infty} f(x) dx = 1\)

  • \(P(a \leq X \leq b) = \int_a^b f(x) dx\)

Relationship with CDF:

\[ F(x)=\int_{-\infty}^x f(t)dt \]

Example: The graphical representation of the standard normal distribution PDF:

\[ \phi(x) = \frac{1}{\sqrt{2\pi}}\exp\left[\frac{x^2}{2}\right] \]

# Create sequence of x values
x <- seq(-4, 4, length.out = 1000)

# Calculate PDF for normal distribution
pdf.normal <- dnorm(x)

# Create data frame for plotting
pdf.df <- data.frame(x = x, PDF = pdf.normal)

# Plot PDF
pdf.plt <- ggplot(pdf.df, aes(x = x, y = PDF)) +
  geom_line(color = "red", linewidth = 1) +
  labs(title = "Probability Density Function (PDF) of Standard Normal",
       x = "percentiles of standard normal distribution", y = "Density Function: f(x)") +
  theme(plot.title = element_text(hjust = 0.5),
        plot.margin = margin(t = 35, r = 20, b = 30, l = 30, unit = "pt"))
ggplotly(pdf.plt)

4 Survival Function

The Survival Function gives the probability that a random variable with density \(f(x)\) exceeds a certain value (\(x\)):

\[ S(x)=P(X>x) = \int_x^\infty f(f) dt=1−F(x) \]

This is particularly useful in survival analysis and reliability engineering in which positive random variables are used to model survival times and system reliability. The distribution is called lifetime distribution.

Example: Visual representation of exponential distribution survival function. Recall that the exponential distribution has density function

\[ f(x) = \lambda e^{-\lambda x}, \ \ 0 \lt x \lt \infty. \]

where \(\lambda > 0\) is called rate (inverse scale).

# Exponential distribution example
x.exp <- seq(0, 5, length.out = 1000)
survival.exp <- 1 - pexp(x.exp, rate = 1)

survival.df <- data.frame(x = x.exp, Survival = survival.exp)

surv.fun.plt <- ggplot(survival.df, aes(x = x, y = Survival)) +
  geom_line(color = "green", linewidth = 1) +
  labs(title = "Survival Function of Exponential(1) Distribution",
       x = "survival time", y = "survial function: S(x)") +
  theme(plot.title = element_text(hjust = 0.5),
        plot.margin = margin(t = 35, r = 20, b = 30, l = 30, unit = "pt"))
ggplotly(surv.fun.plt)

5 Hazard Rate Function

The Hazard Function (or hazard rate) represents the instantaneous failure rate at time \(x\), given survival up to time \(x\):

\[ \lambda(x)=\lim_{\Delta\to 0} \frac{P(x\le X< {x+\Delta x}|X \ge x)}{\Delta x}. \]

Note that

\[ P(x\le X< x+\Delta x) = \int_x^{x+\Delta x} f(t)dt \approx f(x)\Delta x \]

\[ P(x\le X< x+\Delta x|X \ge x) = \frac{P(x\le X< x+\Delta x \cap X \ge x)}{P(X \ge x)} = \frac{f(x)\Delta x}{S(x)} \]

Therefore

\[ \lambda(x)=\lim_{\Delta\to 0} \frac{P(x\le X< {x+\Delta x}|X \ge x)}{\Delta x} = \frac{f(x)}{S(x)}. \]

Components:

  • \(\lambda(x)\): Hazard function

  • \(f(x)\): Probability density function

  • \(S(x)\): Survival function

Example: Graphical representation of Weibull Distribution Hazard Function. Note that Weibull distribution with scale parameter \(\lambda\) and shape parameter \(k\) has density

\[ f(x) = \frac{k}{\lambda}\left(\frac{x}{\lambda} \right)^{k-1}\exp(-x/\lambda )^k, \ \ x \ge 0. \]

We can derive the CDF in the following

\[ F(x) = 1 - \exp[-x/\lambda]^k. \]

# Weibull distribution hazard function
x.weibull <- seq(0.1, 5, length.out = 1000)

# PDF, CDF, and Survival for Weibull(shape=2, scale=1)
pdf.weibull <- dweibull(x.weibull, shape = 2, scale = 1)
survival.weibull <- 1 - pweibull(x.weibull, shape = 2, scale = 1)
hazard.weibull <- pdf.weibull / survival.weibull

hazard.df <- data.frame(
  x = x.weibull,
  Hazard = hazard.weibull,
  PDF = pdf.weibull,
  Survival = survival.weibull
)

haz.plt <- ggplot(hazard.df, aes(x = x, y = Hazard)) +
  geom_line(color = "purple", linewidth = 1) +
  labs(title = "Hazard Function of Weibull(shape=2, scale=1)",
       x = "Survival time", 
       y = expression(paste("Hazard rate function:", lambda, "(x)"))) +
  theme(plot.title = element_text(hjust = 0.5),
        plot.margin = margin(t = 35, r = 20, b = 30, l = 30, unit = "pt"))
ggplotly(haz.plt)

6 Distribution Estimation

We only foxus on the nonparametric estimation of CDF and PDF in this section.

6.1 Empirical Distribution Function (EDF)

The Empirical Distribution Function is a non-parametric estimator of the true CDF:

\[ F_n(x) =\frac{1}{n}\sum_{i=1}^n I(X_i \le x) = \frac{[\ \text{Number of } X_i \le x]}{n} \]

where \(I(\cdot)\) is the indicator function.

Properties:

  • Consistent estimator of the true CDF

  • Step function with jumps at observed data points

  • Unbiased estimator

Example: Empirical Distribution

set.seed(123)
# Generate sample data
sample_data <- rnorm(100, mean = 0, sd = 1)

# Create empirical CDF
empirical_cdf <- ecdf(sample_data)

# Plot empirical vs theoretical CDF
plot_df <- data.frame(
  x = seq(-3, 3, length.out = 1000),
  Empirical = empirical_cdf(seq(-3, 3, length.out = 1000)),
  Theoretical = pnorm(seq(-3, 3, length.out = 1000))
)

plot_df_long <- plot_df %>%
  pivot_longer(cols = c(Empirical, Theoretical), 
               names_to = "Type", values_to = "CDF")

Fn.plt <- ggplot(plot_df_long, aes(x = x, y = CDF, color = Type)) +
  geom_line(linewidth = 1) +
  scale_color_manual(values = c("Empirical" = "blue", "Theoretical" = "red")) +
  labs(title = "Empirical vs Theoretical CDF",
       subtitle = "Sample size n = 100 from Standard Normal",
       x = "x", y = "CDF") +
  theme(plot.title = element_text(hjust = 0.5),
        plot.margin = margin(t = 35, r = 20, b = 30, l = 30, unit = "pt"))
ggplotly(Fn.plt)

6.2 Kernel Density Estimation (KDE)

Kernel Density Estimation provides a smooth estimate of the PDF:

\[ \hat{f}_h(x) = \frac{1}{nh}\sum_{i=1}^n K\left( \frac{x-X_i}{h}\right) = \frac{1}{n}\sum_{i=1}^n\left[\frac{K\left( \frac{x-X_i}{h}\right)}{h} \right] \]

where:

  • \(K(\cdot)\) is the kernel function serves as a weighting function that assigns a probability density to a given point based on its proximity to the data points. Its primary purposes are:
    • Smoothing: Replaces jagged histograms with a continuous, differentiable curve.
    • Local Weighting: Determines how much influence a data point \(x_i\) has on the density estimate at a target point \(x\), based on their proximity.
    • Normalization: Guarantees the final result is a valid probability density function (integrates to 1).
    • Flexibility: The choice of kernel (Gaussian, Epanechnikov, etc.) allows the user to control the smoothness properties of the final estimate, though the bandwidth (h) is far more important.
  • \(h > 0\) is the bandwidth parameter
    • Small h (in-focus): The blobs are very small. You see fine details (individual data points), but the image is very “noisy” and jagged (overfitting).
    • Large h (out-of-focus): The blobs are very large and spread out. The image is very smooth, but you lose all the detail and structure of the data (underfitting).
  • \(n\) is the sample size

Why Kernels Are Related to “Weights”

We will use the Gaussian kernel as an example; other kernels can be explained similarly.

  1. For a given value (not necessary an observed data value) in the domain of the underlying random variable \(X\), \((x - X_i)/h\) is a scaled deviation that measures how the \(i\)-th data value \(x_i\) from the given value \(x\). If \(X_i\) is close to the given value \(x\), \((x - X_i)/h \to 0\).

  2. As \((x - X_i)/h \to 0\), \(K[(x - X_i)/h] \to \text{maximum}\). This implies that data values that are closer to the given value \(x\) will contribute more to the value of the density at \(x\). In other words, kernel is an implicit weight function.

The following figure shows the above logic.

To understand the role of \(h\) in the kernel density estimator, we examine the Gaussian kernel.

\[ K(u) = \frac{1}{\sqrt{2\pi}} \exp(-u^2/2) \]

Substituting the Gaussian kernel into the density estimator, we rewrite \(\hat{f}(x)\) as:

\[ \hat{f}_h(x) = \frac{1}{nh}\sum_{i=1}^n K\left( \frac{x-X_i}{h}\right) = \frac{1}{nh}\sum_{i=1}^n \left(\frac{1}{\sqrt{2\pi}} e^{-\frac{(x-X_i)^2}{2h^2}}\right) \]

\[ = \frac{1}{n}\sum_{i=1}^n \left(\frac{1}{\sqrt{2\pi}\cdot h} e^{-\frac{(x-X_i)^2}{2h^2}}\right) \equiv \frac{1}{n}\sum_{i=1}^n w_{X_i}(x), \]

where the term

\[ w_{X_i}(x)=\frac{1}{\sqrt{2\pi}\cdot h} e^{-\frac{(x-X_i)^2}{2h^2}} \]

is the Gaussian density centered at \(X_i\) and having standard deviation \(h\). Thus, the kernel density estimate is the average of Gaussian densities centered at each data point \(X_i\), all with standard deviation \(h\).


Therefore, in Gaussian kernel, the pre-selected bandwidth \(h\) is the standard deviation.


The R function translate the above Gaussian kernel estimator

my.kerf <- function(in.data, h, out.x){
  n <- length(in.data)     # sample size
  den <- NULL              # density vector to store output values
  for (i in 1:length(out.x)){
    den[i] <- sum(dnorm(out.x[i], mean=in.data, sd = h))/n  # kernel density formula
  }
  den  # return density values based on the out.x values
}


The following figure illustrates the Gaussian kernel density estimation for data generated from a two-component Gaussian mixture model (distribution) with different bandwidths.

  • Built-in KDE: h = 0.4
  • Manual KDE: h = 0.35
# Complete KDE visualization
set.seed(123)
## mixture density with mixing proportion alpha = 0.5 (i.e., 50% to 50%)
dat1 <- rnorm(30, mean = -1, sd = 0.7)
dat2 <- rnorm(30, mean = 2, sd = 0.5)
sample.data <- c(dat1, dat2)
##
## true mixture density
x.true= seq(-3.5, 4,length = 200)
true.den <- 0.5*dnorm(x.true, mean = -1, sd = 0.7) + 0.5*dnorm(x.true, mean = 2, sd = 0.5)

# KDE based on built-in function density(): bandwidth = 0.4
kde <- density(sample.data, bw = 0.35)
kde.y <-approx(kde$x, kde$y, xout = x.true)$y

## Manual calculation with h = 0.35
my.kde <- my.kerf(in.data = sample.data, h = 0.35, out.x = x.true)

# Create individual kernels plot: the bandwidth = standard deviation
individual.kernels <- data.frame()
for (i in 1:length(sample.data)) {
  n <- length(sample.data)
  kernel.y <- dnorm(x.true, mean = sample.data[i], sd = 0.35) / n
  individual.kernels <- rbind(individual.kernels,
                             data.frame(x = x.true, y = kernel.y, 
                                       point = as.factor(i)))
}

# Create comparison data frame
kde.comparison <- data.frame(
  x = x.true,
  true.den = true.den,
  builtin.den = kde.y, 
  my.den = my.kde 
)

## Make long table
kde.comparison.long <- kde.comparison %>%
  pivot_longer(cols = c(true.den, builtin.den, my.den), 
               names_to = "Den.Type", values_to = "Density")

# ggplot KDE with different bandwidths
kde.type.effect.plt <- ggplot(kde.comparison.long,  aes(x = x, y = Density, color = Den.Type)) +
  geom_line(linewidth = 1) +
  geom_line(data = individual.kernels, aes(x = x, y = y, group = point), 
            color = "steelblue", alpha = 0.5, linewidth = 0.3) +
  geom_rug(data = data.frame(x = sample.data), aes(x = x, y = 0), 
           inherit.aes = FALSE, alpha = 0.3) +
  scale_color_manual(
    values = c("true.den" = "blue", "builtin.den" = "red", "my.den" = "orange"),
  ) +
  labs(title = "Kernel Density Estimation Comparison: Built-in vs Manual ",
       x = "x", y = "Density") +
   theme(plot.title = element_text(hjust = 0.5),
        plot.margin = margin(t = 35, r = 20, b = 30, l = 30, unit = "pt"))
ggplotly(kde.type.effect.plt )

Common Kernel Functions:

Gaussian: \(K(u) = \frac{1}{\sqrt{2\pi}} e^{-\frac{1}{2}u^2}\)

Epanechnikov: \(K(u) = \frac{3}{4}(1 - u^2)\) for \(|u| \leq 1\)

Rectangular: \(K(u) = \frac{1}{2}\) for \(|u| \leq 1\)

6.3 Bandwidth Selection

Bandwidth Selection: The bandwidth \(h\) controls the smoothness of the estimate:

  • Small \(h\): under-smoothing (high variance)

  • Large \(h\): over-smoothing (high bias)

The bandwidth \(h\) is a hyperparameter that is chosen by users. There are different algorithms for estimating it based on different criteria. However, there are baseline formulas to select an initial bandwidth. The following is list of formulas for different kernels.

Denote sd = sample standard deviation, IQR = interquartile range, and n = sample size. We can calculate these statistics from the given data.

  • The optimal initial bandwidth selection for a Gaussian kernel, under the assumption of normally distributed data, is the Normal Reference Distribution, rule-of-thumb 0 (nrd0). This serves as the default bandwidth in R’s density() function.

\[ \text{bw} = 0.9 \times \min(\text{sd}, \text{IQR}/1.34) \times n^{-1/5} \]

  • For the Epanechnikov kernel, the initial suggested choice is

\[ \text{bw} = 2.34 \times \text{sd} \times n^{-1/5} \]

  • For rectangular kernel, the initial suggested choice is

\[ \text{bw} = 1.84 \times \text{sd} \times n^{-1/5} \]

Example: Effect of binwidth in Kernel Density Estimation. We still use the 2-component Gaussian mixture as an example. The three bandwidths used in the example are \(h = 0.5\) (default), 0.2 and 1.

# Generate sample data
set.seed(123)
sample.data <- c(rnorm(200, mean = -1, sd = 0.8), 
                 rnorm(200, mean = 2, sd = 1))

# Create KDE with different bandwidths
kde.default <- density(sample.data, kernel = "gaussian")
kde.small.bw <- density(sample.data, bw = 0.2, kernel = "gaussian")
kde.large.bw <- density(sample.data, bw = 1, kernel = "gaussian")

# Create comparison data frame
kde.comparison <- data.frame(
  x = kde.default$x,
  Default = kde.default$y,
  Small.BW = kde.small.bw$y,
  Large.BW = kde.large.bw$y
)

kde.comparison.long <- kde.comparison %>%
  pivot_longer(cols = c(Default, Small.BW, Large.BW), 
               names_to = "Bandwidth", values_to = "Density")

# Plot KDE with different bandwidths
bin.effect.plt <- ggplot(kde.comparison.long, aes(x = x, y = Density, color = Bandwidth)) +
  geom_line(linewidth = 1) +
  geom_rug(data = data.frame(x = sample.data), aes(x = x, y = 0), 
           inherit.aes = FALSE, alpha = 0.3) +
  scale_color_manual(
    values = c("Default" = "blue", "Small.BW" = "red", "Large.BW" = "orange")#,
    #labels = c("Default (bw = 0.5)", "Small (bw = 0.2)", "Large (bw = 1.0)")
  ) +
  labs(title = "Kernel Density Estimation with Different Bandwidths",
       #subtitle = "Sample from mixture of two normal distributions",
       x = "x", y = "Density") +
   theme(plot.title = element_text(hjust = 0.5),
        plot.margin = margin(t = 35, r = 20, b = 30, l = 30, unit = "pt"))
ggplotly(bin.effect.plt )

Comparison of Different Kernels: The next figure shows the difference among the above mentioned three kernel functions.

# Compare different kernel functions
x.kernel <- seq(-3, 3, length.out = 1000)

# Define kernel functions
gaussian.kernel <- dnorm(x.kernel)
epanechnikov.kernel <- ifelse(abs(x.kernel) <= 1, 0.75 * (1 - x.kernel^2), 0)
rectangular.kernel <- ifelse(abs(x.kernel) <= 1, 0.5, 0)

kernels.df <- data.frame(
  x = x.kernel,
  Gaussian = gaussian.kernel,
  Epanechnikov = epanechnikov.kernel,
  Rectangular = rectangular.kernel
)

kernels.long <- kernels.df %>%
  pivot_longer(cols = c(Gaussian, Epanechnikov, Rectangular), 
               names_to = "Kernel", values_to = "Density")

dif.kern.plt  <- ggplot(kernels.long, aes(x = x, y = Density, color = Kernel)) +
  geom_line(linewidth = 1) +
  labs(title = "Common Kernel Functions",
       x = "u", y = "K(u)") +
  theme(plot.title = element_text(hjust = 0.5),
        plot.margin = margin(t = 35, r = 20, b = 30, l = 30, unit = "pt"))
ggplotly(dif.kern.plt )

7 Practical Implementation in R

7.1 Distribution of Commute Times

Consider commute times (in minute) of a company located in a downtown of a city:

12, 15, 17, 18, 19, 20, 20, 21, 22, 23, 24, 25, 26, 27, 55, 58, 60, 62, 63, 65, 
65, 66, 67, 68, 70, 72, 73, 75, 25, 19, 60, 64

We are interested in estimating the distribution of commute times using an empirical distribution function and a Gaussian kernel density estimator.

Solution We first estimate the empirical cumulative distribution function \(\hat{F}_n(t)\) with explicit expression

\[ \hat{F}_n(t) = \frac{\text{Number of } T_i \le t}{n}. \]

times <- c(12, 15, 17, 18, 19, 20, 20, 21, 22, 23, 24, 25, 26, 27, 55, 58, 60, 62, 63, 65, 
65, 66, 67, 68, 70, 72, 73, 75, 25, 19, 60, 64)
uniq.time <- sort(unique(times))   # need to sort the data values
## R function
my.ECDF <- function(indat, outx){
  # outx - a vector of given values
  freq.table <- table(indat)                          # frequency table
  uniq <- as.numeric(names(freq.table))          # unique data values
  rep.time <- as.vector(freq.table)                   # frequencies of the unique data values
  cum.rel.feq <- cumsum(rep.time)/sum(rep.time)       # cumulative relative frequencies: CDF
  cum.prob <- NULL
  for (i in 1:length(outx)){
    intvl.id <- which(uniq <= outx[i])      # identify the index meeting the condition
    cum.prob[i] <- cum.rel.feq[max(intvl.id)] # extract the cumulative prob according to CDF 
  }
  cum.prob             
}
##
plot(uniq.time, my.ECDF(indat = times, outx = uniq.time), type = "s",
     main = "ECDF of Commuting Times",
     xlab = "Commuting Times",
     ylab = "Cumulative Probability")

Next, we estimate the probability density function. Histograms are commonly used to visualize the shape of the distribution.

hist(times, breaks = 18, freq = FALSE)

The distribution of commute times is clearly bimodal. In the following section, we will implement the Gaussian kernel density estimator in R.

Pseudo-code:

Input: in.dat = input data (fix data set) h = bandwidth out.x = input x for evaluating the density (could be a vector) Output: density values evaluated at x

# in.dat = times
# h = 12
# x = c(20,50)
# in.data = times
###
my.kerf <- function(in.data, h, out.x){
  n <- length(in.data)     # sample size
  den <- NULL              # density vector to store output values
  for (i in 1:length(out.x)){
    den[i] <- sum(dnorm(out.x[i], mean=in.data, sd = h))/n  # kernel density formula
  }
  den  # return density values based on the out.x values
}

R Built-in Estimator: density()

Base R has a built-in density estimator density() with the following basic syntax:

density(x, bw = "nrd0", adjust = 1,
        kernel = c("gaussian", "epanechnikov", "rectangular",
                   "triangular", "biweight",
                   "cosine", "optcosine"),
        weights = NULL, window = kernel, width,
        give.Rkern = FALSE, subdensity = FALSE,
        warnWbw = var(weights) > 0,
        n = 512, from, to, cut = 3, ext = 4,
        old.coords = FALSE,
        na.rm = FALSE, ...)

The built-in function approx() is used in conjunction with density() to evaluate the kernel density estimate at specific input values.

approx   (x, y = NULL, xout, method = "linear", n = 50,
          yleft, yright, rule = 1, f = 0, ties = mean, na.rm = TRUE)

Comparing my.kerf() with density()

We expect the two functions to behave similarly. Any minor differences are due to approximation through interpolation.

xx = seq(0,100, length=200)
##  
my.den <- my.kerf(in.data = times, h = 12, out.x = xx)
## density()
kde <- density(times, bw = 12, kernel = "gaussian")
##
kde.y <-approx(kde$x, kde$y, xout = xx)$y
## base R plot
plot(xx, my.den, type = "l", main = "Comparing Kernel Density Estimators",
     xlab = "Commuting Times",
     ylab = "Kernel Density",
     lty = 1,
     lwd = 2,
     col = "navy")
## add density curve based on the built-in density()
lines(xx, kde.y, lty = 2, lwd = 2, col = "orange")
legend("topright", c("my.kerf()", "density()"), lty =c(1,2), lwd = rep(2,2),
       col = c("navy", "orange"), cex = 0.8, bty = "n")

7.2 Simulated Data - Two-component Normal Mixture Distribution

Complete Example: Distribution Analysis

# Generate sample data from a mixture distribution
set.seed(123)
n <- 1000
data.mixture <- c(rnorm(n*0.6, mean = 0, sd = 1), 
                  rnorm(n*0.4, mean = 3, sd = 0.5))

# Calculate empirical CDF
emp.cdf <- ecdf(data.mixture)

# Kernel density estimation
kde.mixture <- density(data.mixture)

# True PDF (for comparison - known in this simulation)
true.pdf <- function(x) {
  0.6 * dnorm(x, mean = 0, sd = 1) + 0.4 * dnorm(x, mean = 3, sd = 0.5)
}

# Create comprehensive plot
x.plot <- seq(-3, 6, length.out = 1000)

plot.data <- data.frame(
  x = x.plot,
  True.PDF = true.pdf(x.plot),
  KDE = approx(kde.mixture$x, kde.mixture$y, xout = x.plot)$y,
  Empirical.CDF = emp.cdf(x.plot),
  True.CDF = 0.6 * pnorm(x.plot, mean = 0, sd = 1) + 
             0.4 * pnorm(x.plot, mean = 3, sd = 0.5)
)

# PDF comparison
pdf.plot <- ggplot(plot.data, aes(x = x)) +
  geom_line(aes(y = True.PDF, color = "True PDF"), linewidth = 1) +
  geom_line(aes(y = KDE, color = "KDE"), linewidth = 1, linetype = "dashed") +
  geom_histogram(data = data.frame(x = data.mixture), 
                 aes(x = x, y = ..density..), 
                 fill = "gray", alpha = 0.5, bins = 30) +
  scale_color_manual(values = c("True PDF" = "gold", "KDE" = "purple")) +
  labs(title = "",
       x = "x", y = "Density", color = "") +
  theme_minimal()

# CDF comparison
cdf.plot <- ggplot(plot.data, aes(x = x)) +
  geom_line(aes(y = True.CDF, color = "True CDF"), linewidth = 1) +
  geom_line(aes(y = Empirical.CDF, color = "Empirical CDF"), 
            linewidth = 1, linetype = "dashed") +
  scale_color_manual(values = c("True CDF" = "red", "Empirical CDF" = "blue")) +
  labs(title = "",
       x = "x", y = "CDF", color = "") +
  theme_minimal()

# Display plots side by side
library(gridExtra)
subplot(ggplotly(pdf.plot), ggplotly(cdf.plot))

8 Summary

The following table summarizes distributions functions and their estimators and properties.

summary_table <- data.frame(
  Function = c("CDF", "PDF", "Survival", "Hazard", "Empirical CDF", "KDE"),
  Definition = c(
    "$F(x) = P(X \\leq x)$",
    "$f(x) = \\frac{d}{dx}F(x)$",
    "$S(x) = P(X > x) = 1 - F(x)$",
    "$\\lambda(x) = \\frac{f(x)}{S(x)}$",
    "$\\hat{F}_n(x) = \\frac{1}{n}\\sum I(X_i \\leq x)$",
    "$\\hat{f}_h(x) = \\frac{1}{nh}\\sum K(\\frac{x-X_i}{h})$"
  ),
  Properties = c(
    "Non-decreasing, right-continuous",
    "Non-negative, integrates to 1",
    "Non-increasing, right-continuous",
    "Instantaneous failure rate",
    "Step function, unbiased",
    "Smooth, depends on bandwidth"
  )
)

kable(summary_table, caption = "Summary of Distribution Functions and Estimators")
Summary of Distribution Functions and Estimators
Function Definition Properties
CDF \(F(x) = P(X \leq x)\) Non-decreasing, right-continuous
PDF \(f(x) = \frac{d}{dx}F(x)\) Non-negative, integrates to 1
Survival \(S(x) = P(X > x) = 1 - F(x)\) Non-increasing, right-continuous
Hazard \(\lambda(x) = \frac{f(x)}{S(x)}\) Instantaneous failure rate
Empirical CDF \(\hat{F}_n(x) = \frac{1}{n}\sum I(X_i \leq x)\) Step function, unbiased
KDE \(\hat{f}_h(x) = \frac{1}{nh}\sum K(\frac{x-X_i}{h})\) Smooth, depends on bandwidth

Key Points

  • CDF provides complete information about the distribution of a random variable

  • PDF describes the relative likelihood of a continuous random variable

  • Survival and Hazard functions are essential for time-to-event data analysis

  • Empirical CDF is a non-parametric estimator that converges to the true CDF

  • Kernel Density Estimation provides smooth PDF estimates but requires careful bandwidth selection

  • The choice of kernel typically has less impact than bandwidth selection in KDE

LS0tDQp0aXRsZTogIlByb2JhYmlsaXR5IERpc3RyaWJ1dGlvbnMgYW5kIE5vbnBhcmFtZXRyaWMgRXN0aW1hdGlvbnMiDQphdXRob3I6ICJDaGVuZyBQZW5nIg0KZGF0ZTogIldlc3QgQ2hlc3RlciBVbml2ZXJzaXR5Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIHRvY19mbG9hdDogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCiAgcGRmX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIGZpZ193aWR0aDogMw0KICAgIGZpZ19oZWlnaHQ6IDMNCiAgd29yZF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAga2VlcF9tZDogeWVzDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQpgYGB7Y3NzLCBlY2hvID0gRkFMU0V9DQojVE9DOjpiZWZvcmUgew0KICBjb250ZW50OiAiVGFibGUgb2YgQ29udGVudHMiOw0KICBmb250LXdlaWdodDogYm9sZDsNCiAgZm9udC1zaXplOiAxLjJlbTsNCiAgZGlzcGxheTogYmxvY2s7DQogIGNvbG9yOiBuYXZ5Ow0KICBtYXJnaW4tYm90dG9tOiAxMHB4Ow0KfQ0KDQoNCmRpdiNUT0MgbGkgeyAgICAgLyogdGFibGUgb2YgY29udGVudCAgKi8NCiAgICBsaXN0LXN0eWxlOnVwcGVyLXJvbWFuOw0KICAgIGJhY2tncm91bmQtaW1hZ2U6bm9uZTsNCiAgICBiYWNrZ3JvdW5kLXJlcGVhdDpub25lOw0KICAgIGJhY2tncm91bmQtcG9zaXRpb246MDsNCn0NCg0KaDEudGl0bGUgeyAgICAvKiBsZXZlbCAxIGhlYWRlciBvZiB0aXRsZSAgKi8NCiAgZm9udC1zaXplOiAyMnB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgY29sb3I6IERhcmtSZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgZm9udC1mYW1pbHk6ICJHaWxsIFNhbnMiLCBzYW5zLXNlcmlmOw0KfQ0KDQpoNC5hdXRob3IgeyAvKiBIZWFkZXIgNCAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICBmb250LXNpemU6IDE1cHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogbmF2eTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KDQpoNC5kYXRlIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgZm9udC1zaXplOiAxOHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgZm9udC1mYW1pbHk6ICJHaWxsIFNhbnMiLCBzYW5zLXNlcmlmOw0KICBjb2xvcjogRGFya0JsdWU7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCg0KaDEgeyAvKiBIZWFkZXIgMSAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMjBweDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQoNCmgyIHsgLyogSGVhZGVyIDIgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDMgeyAvKiBIZWFkZXIgMyAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMTZweDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7IC8qIEhlYWRlciA0IC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogICAgZm9udC1zaXplOiAxNHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQovKiBBZGQgZG90cyBhZnRlciBudW1iZXJlZCBoZWFkZXJzICovDQouaGVhZGVyLXNlY3Rpb24tbnVtYmVyOjphZnRlciB7DQogIGNvbnRlbnQ6ICIuIjsNCg0KYm9keSB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KLmhpZ2hsaWdodG1lIHsgYmFja2dyb3VuZC1jb2xvcjp5ZWxsb3c7IH0NCg0KcCB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KfQ0KYGBgDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0IA0KIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuDQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikNCiAgIGxpYnJhcnkoa25pdHIpDQp9DQppZiAoIXJlcXVpcmUoInBhbmRlciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJwYW5kZXIiKQ0KICAgbGlicmFyeShwYW5kZXIpDQp9DQppZiAoIXJlcXVpcmUoImdncGxvdDIiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCiAgbGlicmFyeShnZ3Bsb3QyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KICBsaWJyYXJ5KHRpZHl2ZXJzZSkNCn0NCg0KaWYgKCFyZXF1aXJlKCJwbG90bHkiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKQ0KICBsaWJyYXJ5KHBsb3RseSkNCn0NCmlmICghcmVxdWlyZSgibWl4dG9vbHMiKSkgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJtaXh0b29scyIpDQogIGxpYnJhcnkobWl4dG9vbHMpDQp9DQojIyBsaWJyYXJ5KG1peHRvb2xzKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICAjIGluY2x1ZGUgY29kZSBjaHVuayBpbiB0aGUgb3V0cHV0IGZpbGUNCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgIyBzb21ldGltZXMsIHlvdSBjb2RlIG1heSBwcm9kdWNlIHdhcm5pbmcgbWVzc2FnZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgeW91IGNhbiBjaG9vc2UgdG8gaW5jbHVkZSB0aGUgd2FybmluZyBtZXNzYWdlcyBpbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHRoZSBvdXRwdXQgZmlsZS4gDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0cyA9IFRSVUUsICAgICMgeW91IGNhbiBhbHNvIGRlY2lkZSB3aGV0aGVyIHRvIGluY2x1ZGUgdGhlIG91dHB1dA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGluIHRoZSBvdXRwdXQgZmlsZS4NCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BDQogICAgICAgICAgICAgICAgICAgICAgKSAgDQpgYGANCg0KXA0KDQoNCiMgSW50cm9kdWN0aW9uDQoNClRoaXMgbm90ZSBjb3ZlcnMgdGhlIGZ1bmRhbWVudGFsIGNvbmNlcHRzIG9mIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbnMgYW5kIHRoZWlyIGNoYXJhY3Rlcml6YXRpb24gdGhyb3VnaCB2YXJpb3VzIGZ1bmN0aW9ucywgZm9sbG93ZWQgYnkgbm9uLXBhcmFtZXRyaWMgZXN0aW1hdGlvbiBtZXRob2RzIGZvciB0aGVzZSBkaXN0cmlidXRpb25zIGZyb20gZGF0YS4gDQoNClByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbnMgZm9ybSB0aGUgYmVkcm9jayBvZiBzdGF0aXN0aWNhbCBhbmFseXNpcywgcHJvdmlkaW5nIHRoZSBtYXRoZW1hdGljYWwgZnJhbWV3b3JrIHRvIGRlc2NyaWJlIGFuZCBhbmFseXplIHJhbmRvbSBwaGVub21lbmEuIFRoZSBjb21wbGV0ZSBiZWhhdmlvciBvZiBhbnkgcmFuZG9tIHZhcmlhYmxlIGNhbiBiZSB1bmRlcnN0b29kIHRocm91Z2ggZm91ciBrZXkgZnVuY3Rpb25zOiANCg0KKiAqKkN1bXVsYXRpdmUgRGlzdHJpYnV0aW9uIEZ1bmN0aW9uIChDREYpKiogY2FwdHVyZXMgdGhlIHByb2JhYmlsaXR5IG9mIG9ic2VydmluZyB2YWx1ZXMgdXAgdG8gYSBjZXJ0YWluIHBvaW50OyANCg0KKiAqKlByb2JhYmlsaXR5IERlbnNpdHkgRnVuY3Rpb24gKFBERikqKiB3aGljaCBkZXNjcmliZXMgdGhlIHJlbGF0aXZlIGxpa2VsaWhvb2QgYXQgc3BlY2lmaWMgdmFsdWVzIGZvciBjb250aW51b3VzIHZhcmlhYmxlczsgDQoNCiogKipTdXJ2aXZhbCBGdW5jdGlvbioqIG1lYXN1cmVzIHRoZSBwcm9iYWJpbGl0eSBvZiBleGNlZWRpbmcgYSBnaXZlbiB2YWx1ZTsgYW5kIA0KDQoqICoqSGF6YXJkIEZ1bmN0aW9uKiogcXVhbnRpZmllcyB0aGUgaW5zdGFudGFuZW91cyByaXNrIG9mIGFuIGV2ZW50IG9jY3VycmluZy4gDQoNClRoZXNlIGludGVycmVsYXRlZCBmdW5jdGlvbnMgcHJvdmlkZSBjb21wbGVtZW50YXJ5IHBlcnNwZWN0aXZlcyBvbiB0aGUgc2FtZSB1bmRlcmx5aW5nIGRpc3RyaWJ1dGlvbiwgZWFjaCBvZmZlcmluZyB1bmlxdWUgaW5zaWdodHMgZm9yIGRpZmZlcmVudCBhbmFseXRpY2FsIGNvbnRleHRzLiBVbmRlcnN0YW5kaW5nIHRoZXNlIGZ1bmRhbWVudGFsIGNvbmNlcHRzIGlzIGNydWNpYWwgbm90IG9ubHkgZm9yIHRoZW9yZXRpY2FsIHByb2JhYmlsaXR5IGJ1dCBhbHNvIGZvciBwcmFjdGljYWwgc3RhdGlzdGljYWwgaW5mZXJlbmNlLCBhcyB0aGV5IGVuYWJsZSB1cyB0byBtb3ZlIGZyb20gYWJzdHJhY3QgbWF0aGVtYXRpY2FsIGRlc2NyaXB0aW9ucyB0byBjb25jcmV0ZSBkYXRhIGFuYWx5c2lzIHRocm91Z2ggZXN0aW1hdGlvbiB0ZWNobmlxdWVzIGxpa2UgZW1waXJpY2FsIGRpc3RyaWJ1dGlvbnMgYW5kIGtlcm5lbCBkZW5zaXR5IGVzdGltYXRpb24uDQoNCg0KIyBQcm9iYWJpbGl0eSBEaXN0cmlidXRpb24gRnVuY3Rpb25zDQoNClRoaXMgc2VjdGlvbiBkaXNjdXNzZXMgdGhlIGZvdXIga2V5IGZ1bmN0aW9ucyB0aGF0IGNoYXJhY3Rlcml6ZSBhIGdlbmVyYWwgZGlzdHJpYnV0aW9uLg0KDQojIyBDdW11bGF0aXZlIERpc3RyaWJ1dGlvbiBGdW5jdGlvbiAoQ0RGKQ0KDQpUaGUgQ3VtdWxhdGl2ZSBEaXN0cmlidXRpb24gRnVuY3Rpb24gKENERikgb2YgYSByYW5kb20gdmFyaWFibGUgJFgkIGlzIGRlZmluZWQgYXM6DQoNCiQkDQpGKHgpPVAoWCBcbGUgeCkNCiQkDQoqKlByb3BlcnRpZXMqKjoNCg0KKiAkRih4KSQgaXMgbm9uLWRlY3JlYXNpbmcNCg0KKiAkXGxpbV97eCBcdG8gLVxpbmZ0eX0gRih4KSA9IDAkDQoNCiogJFxsaW1fe3ggXHRvIFxpbmZ0eX0gRih4KSA9IDEkDQoNCiogJEYoeCkkIGlzIHJpZ2h0LWNvbnRpbnVvdXMNCg0KDQoqKkV4YW1wbGUqKjogQSBncmFwaGljYWwgcmVwcmVzZW50YXRpb24gb2YgdGhlICoqc3RhbmRhcmQgbm9ybWFsIGRpc3RyaWJ1dGlvbiBDREYqKjoNCg0KJCQNClxQaGkoeCkgPSBGKHgpID0gXGludF97LVxpbmZ0eX1eeCBcZnJhY3sxfXtcc3FydHsyXHBpfX0gXGV4cFxsZWZ0Wy1cZnJhY3t0XjJ9ezJ9IFxyaWdodF1kdCwgXCBcIC1caW5mdHkgPCB4IDwgXGluZnR5Lg0KJCQNCg0KYGBge3J9DQojIENyZWF0ZSBzZXF1ZW5jZSBvZiB4IHZhbHVlcw0KeCA8LSBzZXEoLTQsIDQsIGxlbmd0aC5vdXQgPSAxMDAwKQ0KDQojIENhbGN1bGF0ZSBDREYgZm9yIG5vcm1hbCBkaXN0cmlidXRpb24NCmNkZi5ub3JtYWwgPC0gcG5vcm0oeCkgICAgICAjIENERg0KDQojIENyZWF0ZSBkYXRhIGZyYW1lIGZvciBwbG90dGluZw0KY2RmLmRmIDwtIGRhdGEuZnJhbWUoeCA9IHgsIENERiA9IGNkZi5ub3JtYWwpDQoNCiMgUGxvdCBDREYNCmNkZi5wbHQgPC0gZ2dwbG90KGNkZi5kZiwgYWVzKHggPSB4LCB5ID0gQ0RGKSkgKw0KICBnZW9tX2xpbmUoY29sb3IgPSAiYmx1ZSIsIGxpbmV3aWR0aCA9IDEpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYygwLCAxKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgYWxwaGEgPSAwLjUpICsNCiAgbGFicyh0aXRsZSA9ICJDdW11bGF0aXZlIERpc3RyaWJ1dGlvbiBGdW5jdGlvbiAoQ0RGKSBvZiBTdGFuZGFyZCBOb3JtYWwiLA0KICAgICAgIHggPSAicGVyY2VudGlsZXMgb2Ygc3RhbmRhcmQgbm9ybWFsIGRpc3RyaWJ1dGlvbiIsIHkgPSAiQ3VtdWxhdGl2ZSBEaXN0cmlidXRpb246IEYoeCkiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLA0KICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbih0ID0gMzUsIHIgPSAyMCwgYiA9IDMwLCBsID0gMzAsIHVuaXQgPSAicHQiKSkNCmdncGxvdGx5KGNkZi5wbHQpDQpgYGANCg0KIyBQcm9iYWJpbGl0eSBEZW5zaXR5IEZ1bmN0aW9uIChQREYpDQoNCkZvciBjb250aW51b3VzIHJhbmRvbSB2YXJpYWJsZXMsIHRoZSBQcm9iYWJpbGl0eSBEZW5zaXR5IEZ1bmN0aW9uIChQREYpIGlzIHRoZSBkZXJpdmF0aXZlIG9mIHRoZSBDREY6DQoNCiQkDQpmKHgpPSBcZnJhY3tkRih4KX17ZHh9DQokJA0KDQoNCioqUHJvcGVydGllcyoqOg0KDQoqICRmKHgpIFxnZXEgMCQgZm9yIGFsbCAkeCQNCg0KKiAkXGludF97LVxpbmZ0eX1ee1xpbmZ0eX0gZih4KSBkeCA9IDEkDQoNCiogJFAoYSBcbGVxIFggXGxlcSBiKSA9IFxpbnRfYV5iIGYoeCkgZHgkDQoNCioqUmVsYXRpb25zaGlwIHdpdGggQ0RGKio6DQoNCiQkDQpGKHgpPVxpbnRfey1caW5mdHl9XnggZih0KWR0DQokJA0KDQoNCioqRXhhbXBsZSoqOiBUaGUgZ3JhcGhpY2FsIHJlcHJlc2VudGF0aW9uIG9mIHRoZSAqKnN0YW5kYXJkIG5vcm1hbCBkaXN0cmlidXRpb24gUERGKio6DQoNCiQkDQpccGhpKHgpID0gXGZyYWN7MX17XHNxcnR7MlxwaX19XGV4cFxsZWZ0W1xmcmFje3heMn17Mn1ccmlnaHRdDQokJA0KDQpgYGB7cn0NCiMgQ3JlYXRlIHNlcXVlbmNlIG9mIHggdmFsdWVzDQp4IDwtIHNlcSgtNCwgNCwgbGVuZ3RoLm91dCA9IDEwMDApDQoNCiMgQ2FsY3VsYXRlIFBERiBmb3Igbm9ybWFsIGRpc3RyaWJ1dGlvbg0KcGRmLm5vcm1hbCA8LSBkbm9ybSh4KQ0KDQojIENyZWF0ZSBkYXRhIGZyYW1lIGZvciBwbG90dGluZw0KcGRmLmRmIDwtIGRhdGEuZnJhbWUoeCA9IHgsIFBERiA9IHBkZi5ub3JtYWwpDQoNCiMgUGxvdCBQREYNCnBkZi5wbHQgPC0gZ2dwbG90KHBkZi5kZiwgYWVzKHggPSB4LCB5ID0gUERGKSkgKw0KICBnZW9tX2xpbmUoY29sb3IgPSAicmVkIiwgbGluZXdpZHRoID0gMSkgKw0KICBsYWJzKHRpdGxlID0gIlByb2JhYmlsaXR5IERlbnNpdHkgRnVuY3Rpb24gKFBERikgb2YgU3RhbmRhcmQgTm9ybWFsIiwNCiAgICAgICB4ID0gInBlcmNlbnRpbGVzIG9mIHN0YW5kYXJkIG5vcm1hbCBkaXN0cmlidXRpb24iLCB5ID0gIkRlbnNpdHkgRnVuY3Rpb246IGYoeCkiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLA0KICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbih0ID0gMzUsIHIgPSAyMCwgYiA9IDMwLCBsID0gMzAsIHVuaXQgPSAicHQiKSkNCmdncGxvdGx5KHBkZi5wbHQpDQpgYGANCg0KDQojIFN1cnZpdmFsIEZ1bmN0aW9uDQoNClRoZSBTdXJ2aXZhbCBGdW5jdGlvbiBnaXZlcyB0aGUgcHJvYmFiaWxpdHkgdGhhdCBhIHJhbmRvbSB2YXJpYWJsZSB3aXRoIGRlbnNpdHkgJGYoeCkkIGV4Y2VlZHMgYSBjZXJ0YWluIHZhbHVlICgkeCQpOg0KDQokJA0KUyh4KT1QKFg+eCkgPSBcaW50X3heXGluZnR5IGYoZikgZHQ9MeKIkkYoeCkNCiQkDQoNCg0KVGhpcyBpcyBwYXJ0aWN1bGFybHkgdXNlZnVsIGluIHN1cnZpdmFsIGFuYWx5c2lzIGFuZCByZWxpYWJpbGl0eSBlbmdpbmVlcmluZyBpbiB3aGljaCBwb3NpdGl2ZSByYW5kb20gdmFyaWFibGVzIGFyZSB1c2VkIHRvIG1vZGVsIHN1cnZpdmFsIHRpbWVzIGFuZCBzeXN0ZW0gcmVsaWFiaWxpdHkuIFRoZSBkaXN0cmlidXRpb24gaXMgY2FsbGVkIGxpZmV0aW1lIGRpc3RyaWJ1dGlvbi4gDQoNCg0KKipFeGFtcGxlKio6IFZpc3VhbCByZXByZXNlbnRhdGlvbiBvZiAqKmV4cG9uZW50aWFsIGRpc3RyaWJ1dGlvbiBzdXJ2aXZhbCBmdW5jdGlvbioqLiBSZWNhbGwgdGhhdCB0aGUgZXhwb25lbnRpYWwgZGlzdHJpYnV0aW9uIGhhcyBkZW5zaXR5IGZ1bmN0aW9uDQoNCiQkDQpmKHgpID0gXGxhbWJkYSBlXnstXGxhbWJkYSB4fSwgXCBcIDAgXGx0IHggXGx0IFxpbmZ0eS4NCiQkDQoNCndoZXJlICRcbGFtYmRhID4gMCQgaXMgY2FsbGVkICoqcmF0ZSoqIChpbnZlcnNlIHNjYWxlKS4NCg0KDQpgYGB7cn0NCiMgRXhwb25lbnRpYWwgZGlzdHJpYnV0aW9uIGV4YW1wbGUNCnguZXhwIDwtIHNlcSgwLCA1LCBsZW5ndGgub3V0ID0gMTAwMCkNCnN1cnZpdmFsLmV4cCA8LSAxIC0gcGV4cCh4LmV4cCwgcmF0ZSA9IDEpDQoNCnN1cnZpdmFsLmRmIDwtIGRhdGEuZnJhbWUoeCA9IHguZXhwLCBTdXJ2aXZhbCA9IHN1cnZpdmFsLmV4cCkNCg0Kc3Vydi5mdW4ucGx0IDwtIGdncGxvdChzdXJ2aXZhbC5kZiwgYWVzKHggPSB4LCB5ID0gU3Vydml2YWwpKSArDQogIGdlb21fbGluZShjb2xvciA9ICJncmVlbiIsIGxpbmV3aWR0aCA9IDEpICsNCiAgbGFicyh0aXRsZSA9ICJTdXJ2aXZhbCBGdW5jdGlvbiBvZiBFeHBvbmVudGlhbCgxKSBEaXN0cmlidXRpb24iLA0KICAgICAgIHggPSAic3Vydml2YWwgdGltZSIsIHkgPSAic3VydmlhbCBmdW5jdGlvbjogUyh4KSIpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAzNSwgciA9IDIwLCBiID0gMzAsIGwgPSAzMCwgdW5pdCA9ICJwdCIpKQ0KZ2dwbG90bHkoc3Vydi5mdW4ucGx0KQ0KYGBgDQoNCg0KDQojIEhhemFyZCBSYXRlIEZ1bmN0aW9uDQoNClRoZSBIYXphcmQgRnVuY3Rpb24gKG9yIGhhemFyZCByYXRlKSByZXByZXNlbnRzIHRoZSBpbnN0YW50YW5lb3VzIGZhaWx1cmUgcmF0ZSBhdCB0aW1lICR4JCwgZ2l2ZW4gc3Vydml2YWwgdXAgdG8gdGltZSAkeCQ6DQoNCiQkDQpcbGFtYmRhKHgpPVxsaW1fe1xEZWx0YVx0byAwfSBcZnJhY3tQKHhcbGUgWDwge3grXERlbHRhIHh9fFggXGdlIHgpfXtcRGVsdGEgeH0uIA0KJCQgDQogDQpOb3RlIHRoYXQNCg0KJCQNClAoeFxsZSBYPCB4K1xEZWx0YSB4KSA9IFxpbnRfeF57eCtcRGVsdGEgeH0gZih0KWR0IFxhcHByb3ggZih4KVxEZWx0YSB4DQokJA0KDQokJA0KUCh4XGxlIFg8IHgrXERlbHRhIHh8WCBcZ2UgeCkgPSBcZnJhY3tQKHhcbGUgWDwgeCtcRGVsdGEgeCBcY2FwIFggXGdlIHgpfXtQKFggXGdlIHgpfSA9IFxmcmFje2YoeClcRGVsdGEgeH17Uyh4KX0NCiQkDQoNClRoZXJlZm9yZQ0KDQokJA0KXGxhbWJkYSh4KT1cbGltX3tcRGVsdGFcdG8gMH0gXGZyYWN7UCh4XGxlIFg8IHt4K1xEZWx0YSB4fXxYIFxnZSB4KX17XERlbHRhIHh9ID0gXGZyYWN7Zih4KX17Uyh4KX0uIA0KJCQgDQoNCg0KDQoqKkNvbXBvbmVudHMqKjoNCg0KKiAkXGxhbWJkYSh4KSQ6IEhhemFyZCBmdW5jdGlvbg0KDQoqICRmKHgpJDogUHJvYmFiaWxpdHkgZGVuc2l0eSBmdW5jdGlvbg0KDQoqICRTKHgpJDogU3Vydml2YWwgZnVuY3Rpb24NCg0KDQoqKkV4YW1wbGUqKjogR3JhcGhpY2FsIHJlcHJlc2VudGF0aW9uIG9mICoqV2VpYnVsbCBEaXN0cmlidXRpb24gSGF6YXJkIEZ1bmN0aW9uKiouIE5vdGUgdGhhdCBXZWlidWxsIGRpc3RyaWJ1dGlvbiB3aXRoIHNjYWxlIHBhcmFtZXRlciAkXGxhbWJkYSQgYW5kIHNoYXBlIHBhcmFtZXRlciAkayQgaGFzIGRlbnNpdHkNCg0KJCQNCmYoeCkgPSBcZnJhY3trfXtcbGFtYmRhfVxsZWZ0KFxmcmFje3h9e1xsYW1iZGF9IFxyaWdodClee2stMX1cZXhwKC14L1xsYW1iZGEgKV5rLCBcIFwgeCBcZ2UgMC4gDQokJA0KDQpXZSBjYW4gZGVyaXZlIHRoZSBDREYgaW4gdGhlIGZvbGxvd2luZw0KDQokJA0KRih4KSA9IDEgLSBcZXhwWy14L1xsYW1iZGFdXmsuDQokJA0KDQoNCmBgYHtyfQ0KIyBXZWlidWxsIGRpc3RyaWJ1dGlvbiBoYXphcmQgZnVuY3Rpb24NCngud2VpYnVsbCA8LSBzZXEoMC4xLCA1LCBsZW5ndGgub3V0ID0gMTAwMCkNCg0KIyBQREYsIENERiwgYW5kIFN1cnZpdmFsIGZvciBXZWlidWxsKHNoYXBlPTIsIHNjYWxlPTEpDQpwZGYud2VpYnVsbCA8LSBkd2VpYnVsbCh4LndlaWJ1bGwsIHNoYXBlID0gMiwgc2NhbGUgPSAxKQ0Kc3Vydml2YWwud2VpYnVsbCA8LSAxIC0gcHdlaWJ1bGwoeC53ZWlidWxsLCBzaGFwZSA9IDIsIHNjYWxlID0gMSkNCmhhemFyZC53ZWlidWxsIDwtIHBkZi53ZWlidWxsIC8gc3Vydml2YWwud2VpYnVsbA0KDQpoYXphcmQuZGYgPC0gZGF0YS5mcmFtZSgNCiAgeCA9IHgud2VpYnVsbCwNCiAgSGF6YXJkID0gaGF6YXJkLndlaWJ1bGwsDQogIFBERiA9IHBkZi53ZWlidWxsLA0KICBTdXJ2aXZhbCA9IHN1cnZpdmFsLndlaWJ1bGwNCikNCg0KaGF6LnBsdCA8LSBnZ3Bsb3QoaGF6YXJkLmRmLCBhZXMoeCA9IHgsIHkgPSBIYXphcmQpKSArDQogIGdlb21fbGluZShjb2xvciA9ICJwdXJwbGUiLCBsaW5ld2lkdGggPSAxKSArDQogIGxhYnModGl0bGUgPSAiSGF6YXJkIEZ1bmN0aW9uIG9mIFdlaWJ1bGwoc2hhcGU9Miwgc2NhbGU9MSkiLA0KICAgICAgIHggPSAiU3Vydml2YWwgdGltZSIsIA0KICAgICAgIHkgPSBleHByZXNzaW9uKHBhc3RlKCJIYXphcmQgcmF0ZSBmdW5jdGlvbjoiLCBsYW1iZGEsICIoeCkiKSkpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAzNSwgciA9IDIwLCBiID0gMzAsIGwgPSAzMCwgdW5pdCA9ICJwdCIpKQ0KZ2dwbG90bHkoaGF6LnBsdCkNCmBgYA0KDQoNCiMgRGlzdHJpYnV0aW9uIEVzdGltYXRpb24NCg0KV2Ugb25seSBmb3h1cyBvbiB0aGUgbm9ucGFyYW1ldHJpYyBlc3RpbWF0aW9uIG9mIENERiBhbmQgUERGIGluIHRoaXMgc2VjdGlvbi4NCg0KIyMgRW1waXJpY2FsIERpc3RyaWJ1dGlvbiBGdW5jdGlvbiAoRURGKQ0KDQpUaGUgRW1waXJpY2FsIERpc3RyaWJ1dGlvbiBGdW5jdGlvbiBpcyBhIG5vbi1wYXJhbWV0cmljIGVzdGltYXRvciBvZiB0aGUgdHJ1ZSBDREY6DQoNCiQkDQpGX24oeCkgPVxmcmFjezF9e259XHN1bV97aT0xfV5uIEkoWF9pIFxsZSB4KSA9IFxmcmFje1tcIFx0ZXh0e051bWJlciBvZiAgfSBYX2kgXGxlIHhdfXtufQ0KJCQNCg0Kd2hlcmUgJEkoXGNkb3QpJCBpcyB0aGUgaW5kaWNhdG9yIGZ1bmN0aW9uLg0KDQoqKlByb3BlcnRpZXMqKjoNCg0KKiAqKkNvbnNpc3RlbnQgZXN0aW1hdG9yKiogb2YgdGhlIHRydWUgQ0RGDQoNCiogU3RlcCBmdW5jdGlvbiB3aXRoIGp1bXBzIGF0IG9ic2VydmVkIGRhdGEgcG9pbnRzDQoNCiogVW5iaWFzZWQgZXN0aW1hdG9yDQoNCg0KKipFeGFtcGxlKio6IEVtcGlyaWNhbCBEaXN0cmlidXRpb24NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQojIEdlbmVyYXRlIHNhbXBsZSBkYXRhDQpzYW1wbGVfZGF0YSA8LSBybm9ybSgxMDAsIG1lYW4gPSAwLCBzZCA9IDEpDQoNCiMgQ3JlYXRlIGVtcGlyaWNhbCBDREYNCmVtcGlyaWNhbF9jZGYgPC0gZWNkZihzYW1wbGVfZGF0YSkNCg0KIyBQbG90IGVtcGlyaWNhbCB2cyB0aGVvcmV0aWNhbCBDREYNCnBsb3RfZGYgPC0gZGF0YS5mcmFtZSgNCiAgeCA9IHNlcSgtMywgMywgbGVuZ3RoLm91dCA9IDEwMDApLA0KICBFbXBpcmljYWwgPSBlbXBpcmljYWxfY2RmKHNlcSgtMywgMywgbGVuZ3RoLm91dCA9IDEwMDApKSwNCiAgVGhlb3JldGljYWwgPSBwbm9ybShzZXEoLTMsIDMsIGxlbmd0aC5vdXQgPSAxMDAwKSkNCikNCg0KcGxvdF9kZl9sb25nIDwtIHBsb3RfZGYgJT4lDQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhFbXBpcmljYWwsIFRoZW9yZXRpY2FsKSwgDQogICAgICAgICAgICAgICBuYW1lc190byA9ICJUeXBlIiwgdmFsdWVzX3RvID0gIkNERiIpDQoNCkZuLnBsdCA8LSBnZ3Bsb3QocGxvdF9kZl9sb25nLCBhZXMoeCA9IHgsIHkgPSBDREYsIGNvbG9yID0gVHlwZSkpICsNCiAgZ2VvbV9saW5lKGxpbmV3aWR0aCA9IDEpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIkVtcGlyaWNhbCIgPSAiYmx1ZSIsICJUaGVvcmV0aWNhbCIgPSAicmVkIikpICsNCiAgbGFicyh0aXRsZSA9ICJFbXBpcmljYWwgdnMgVGhlb3JldGljYWwgQ0RGIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJTYW1wbGUgc2l6ZSBuID0gMTAwIGZyb20gU3RhbmRhcmQgTm9ybWFsIiwNCiAgICAgICB4ID0gIngiLCB5ID0gIkNERiIpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAzNSwgciA9IDIwLCBiID0gMzAsIGwgPSAzMCwgdW5pdCA9ICJwdCIpKQ0KZ2dwbG90bHkoRm4ucGx0KQ0KYGBgDQoNCg0KIyMgS2VybmVsIERlbnNpdHkgRXN0aW1hdGlvbiAoS0RFKQ0KDQpLZXJuZWwgRGVuc2l0eSBFc3RpbWF0aW9uIHByb3ZpZGVzIGEgc21vb3RoIGVzdGltYXRlIG9mIHRoZSBQREY6DQoNCiQkDQpcaGF0e2Z9X2goeCkgPSBcZnJhY3sxfXtuaH1cc3VtX3tpPTF9Xm4gS1xsZWZ0KCBcZnJhY3t4LVhfaX17aH1ccmlnaHQpID0gXGZyYWN7MX17bn1cc3VtX3tpPTF9Xm5cbGVmdFtcZnJhY3tLXGxlZnQoIFxmcmFje3gtWF9pfXtofVxyaWdodCl9e2h9IFxyaWdodF0NCiQkDQoNCndoZXJlOg0KDQoqICRLKFxjZG90KSQgaXMgdGhlICoqa2VybmVsIGZ1bmN0aW9uKiogc2VydmVzIGFzIGEgKip3ZWlnaHRpbmcgZnVuY3Rpb24qKiB0aGF0IGFzc2lnbnMgYSBwcm9iYWJpbGl0eSBkZW5zaXR5IHRvIGEgZ2l2ZW4gcG9pbnQgYmFzZWQgb24gaXRzIHByb3hpbWl0eSB0byB0aGUgZGF0YSBwb2ludHMuIEl0cyBwcmltYXJ5IHB1cnBvc2VzIGFyZToNCiAgKyAqKlNtb290aGluZyoqOiBSZXBsYWNlcyBqYWdnZWQgaGlzdG9ncmFtcyB3aXRoIGEgY29udGludW91cywgZGlmZmVyZW50aWFibGUgY3VydmUuDQogICsgKipMb2NhbCBXZWlnaHRpbmcqKjogRGV0ZXJtaW5lcyBob3cgbXVjaCBpbmZsdWVuY2UgYSBkYXRhIHBvaW50ICR4X2kkIGhhcyBvbiB0aGUgZGVuc2l0eSBlc3RpbWF0ZSBhdCBhIHRhcmdldCBwb2ludCAkeCQsIGJhc2VkIG9uIHRoZWlyIHByb3hpbWl0eS4NCiAgKyAqKk5vcm1hbGl6YXRpb24qKjogR3VhcmFudGVlcyB0aGUgZmluYWwgcmVzdWx0IGlzIGEgdmFsaWQgcHJvYmFiaWxpdHkgZGVuc2l0eSBmdW5jdGlvbiAoaW50ZWdyYXRlcyB0byAxKS4NCiAgKyAqKkZsZXhpYmlsaXR5Kio6IFRoZSBjaG9pY2Ugb2Yga2VybmVsIChHYXVzc2lhbiwgRXBhbmVjaG5pa292LCBldGMuKSBhbGxvd3MgdGhlIHVzZXIgdG8gY29udHJvbCB0aGUgc21vb3RobmVzcyBwcm9wZXJ0aWVzIG9mIHRoZSBmaW5hbCBlc3RpbWF0ZSwgdGhvdWdoIHRoZSBiYW5kd2lkdGggKGgpIGlzIGZhciBtb3JlIGltcG9ydGFudC4NCg0KKiAkaCA+IDAkIGlzIHRoZSBiYW5kd2lkdGggcGFyYW1ldGVyDQogICsgKipTbWFsbCBoIChpbi1mb2N1cykqKjogVGhlIGJsb2JzIGFyZSB2ZXJ5IHNtYWxsLiBZb3Ugc2VlIGZpbmUgZGV0YWlscyAoaW5kaXZpZHVhbCBkYXRhIHBvaW50cyksIGJ1dCB0aGUgaW1hZ2UgaXMgdmVyeSAibm9pc3kiIGFuZCBqYWdnZWQgKG92ZXJmaXR0aW5nKS4NCiAgKyBMYXJnZSBoIChvdXQtb2YtZm9jdXMpOiBUaGUgYmxvYnMgYXJlIHZlcnkgbGFyZ2UgYW5kIHNwcmVhZCBvdXQuIFRoZSBpbWFnZSBpcyB2ZXJ5IHNtb290aCwgYnV0IHlvdSBsb3NlIGFsbCB0aGUgZGV0YWlsIGFuZCBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgKHVuZGVyZml0dGluZykuDQogDQoqICRuJCBpcyB0aGUgc2FtcGxlIHNpemUNCg0KDQoqKldoeSBLZXJuZWxzIEFyZSBSZWxhdGVkIHRvICJXZWlnaHRzIioqDQoNCg0KV2Ugd2lsbCB1c2UgdGhlIEdhdXNzaWFuIGtlcm5lbCBhcyBhbiBleGFtcGxlOyBvdGhlciBrZXJuZWxzIGNhbiBiZSBleHBsYWluZWQgc2ltaWxhcmx5LiANCg0KMS4gRm9yIGEgZ2l2ZW4gdmFsdWUgKG5vdCBuZWNlc3NhcnkgYW4gb2JzZXJ2ZWQgZGF0YSB2YWx1ZSkgaW4gdGhlIGRvbWFpbiBvZiB0aGUgdW5kZXJseWluZyByYW5kb20gdmFyaWFibGUgJFgkLCAkKHggLSBYX2kpL2gkIGlzIGEgKipzY2FsZWQgZGV2aWF0aW9uKiogdGhhdCBtZWFzdXJlcyBob3cgdGhlICRpJC10aCBkYXRhIHZhbHVlICR4X2kkIGZyb20gdGhlIGdpdmVuIHZhbHVlICR4JC4gSWYgJFhfaSQgaXMgY2xvc2UgdG8gdGhlIGdpdmVuIHZhbHVlICR4JCwgJCh4IC0gWF9pKS9oIFx0byAwJC4NCg0KMi4gQXMgJCh4IC0gWF9pKS9oIFx0byAwJCwgJEtbKHggLSBYX2kpL2hdIFx0byBcdGV4dHttYXhpbXVtfSQuIFRoaXMgaW1wbGllcyB0aGF0IGRhdGEgdmFsdWVzIHRoYXQgYXJlIGNsb3NlciB0byB0aGUgZ2l2ZW4gdmFsdWUgJHgkIHdpbGwgY29udHJpYnV0ZSBtb3JlIHRvIHRoZSB2YWx1ZSBvZiB0aGUgZGVuc2l0eSBhdCAkeCQuIEluIG90aGVyIHdvcmRzLCBrZXJuZWwgaXMgYW4gKippbXBsaWNpdCB3ZWlnaHQgZnVuY3Rpb24qKi4NCg0KVGhlIGZvbGxvd2luZyBmaWd1cmUgc2hvd3MgdGhlIGFib3ZlIGxvZ2ljLg0KDQpgYGB7ciAgZWNobyA9IEZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0gIjcwJSJ9DQppbmNsdWRlX2dyYXBoaWNzKCJLZXJuZWxXZWlnaHQucG5nIikNCmBgYA0KDQoNCioqVG8gdW5kZXJzdGFuZCB0aGUgcm9sZSBvZiAkaCQgaW4gdGhlIGtlcm5lbCBkZW5zaXR5IGVzdGltYXRvcioqLCB3ZSBleGFtaW5lIHRoZSBHYXVzc2lhbiBrZXJuZWwuDQoNCiQkDQpLKHUpID0gXGZyYWN7MX17XHNxcnR7MlxwaX19IFxleHAoLXVeMi8yKQ0KJCQNCg0KU3Vic3RpdHV0aW5nIHRoZSBHYXVzc2lhbiBrZXJuZWwgaW50byB0aGUgZGVuc2l0eSBlc3RpbWF0b3IsIHdlIHJld3JpdGUgJFxoYXR7Zn0oeCkkIGFzOg0KDQokJA0KXGhhdHtmfV9oKHgpID0gXGZyYWN7MX17bmh9XHN1bV97aT0xfV5uIEtcbGVmdCggXGZyYWN7eC1YX2l9e2h9XHJpZ2h0KSA9IFxmcmFjezF9e25ofVxzdW1fe2k9MX1ebiBcbGVmdChcZnJhY3sxfXtcc3FydHsyXHBpfX0gZV57LVxmcmFjeyh4LVhfaSleMn17MmheMn19XHJpZ2h0KQ0KJCQNCg0KJCQNCj0gXGZyYWN7MX17bn1cc3VtX3tpPTF9Xm4gXGxlZnQoXGZyYWN7MX17XHNxcnR7MlxwaX1cY2RvdCBofSBlXnstXGZyYWN7KHgtWF9pKV4yfXsyaF4yfX1ccmlnaHQpIFxlcXVpdiBcZnJhY3sxfXtufVxzdW1fe2k9MX1ebiB3X3tYX2l9KHgpLA0KJCQNCg0Kd2hlcmUgdGhlIHRlcm0gDQoNCiQkDQp3X3tYX2l9KHgpPVxmcmFjezF9e1xzcXJ0ezJccGl9XGNkb3QgaH0gZV57LVxmcmFjeyh4LVhfaSleMn17MmheMn19DQokJA0KDQppcyB0aGUgR2F1c3NpYW4gZGVuc2l0eSBjZW50ZXJlZCBhdCAkWF9pJCBhbmQgaGF2aW5nIDxmb250IGNvbG9yID0gInJlZCI+KipzdGFuZGFyZCBkZXZpYXRpb24gJGgkKio8L2ZvbnQ+LiBUaHVzLCA8Zm9udCBjb2xvciA9ICJibHVlIj4qKnRoZSBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0ZSBpcyB0aGUgYXZlcmFnZSBvZiBHYXVzc2lhbiBkZW5zaXRpZXMgY2VudGVyZWQgYXQgZWFjaCBkYXRhIHBvaW50ICRYX2kkLCBhbGwgd2l0aCBzdGFuZGFyZCBkZXZpYXRpb24gJGgkKiouPC9mb250Pg0KDQpcDQoNCjxmb250IGNvbG9yID0gImRhcmtyZWQiIHNpemUgPSAzPioqVGhlcmVmb3JlLCBpbiBHYXVzc2lhbiBrZXJuZWwsIHRoZSBwcmUtc2VsZWN0ZWQgYmFuZHdpZHRoICRoJCBpcyB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uLioqPC9mb250Pg0KDQpcDQoNClRoZSBSIGZ1bmN0aW9uIHRyYW5zbGF0ZSB0aGUgYWJvdmUgR2F1c3NpYW4ga2VybmVsIGVzdGltYXRvcg0KDQpgYGB7cn0NCm15LmtlcmYgPC0gZnVuY3Rpb24oaW4uZGF0YSwgaCwgb3V0Lngpew0KICBuIDwtIGxlbmd0aChpbi5kYXRhKSAgICAgIyBzYW1wbGUgc2l6ZQ0KICBkZW4gPC0gTlVMTCAgICAgICAgICAgICAgIyBkZW5zaXR5IHZlY3RvciB0byBzdG9yZSBvdXRwdXQgdmFsdWVzDQogIGZvciAoaSBpbiAxOmxlbmd0aChvdXQueCkpew0KICAgIGRlbltpXSA8LSBzdW0oZG5vcm0ob3V0LnhbaV0sIG1lYW49aW4uZGF0YSwgc2QgPSBoKSkvbiAgIyBrZXJuZWwgZGVuc2l0eSBmb3JtdWxhDQogIH0NCiAgZGVuICAjIHJldHVybiBkZW5zaXR5IHZhbHVlcyBiYXNlZCBvbiB0aGUgb3V0LnggdmFsdWVzDQp9DQpgYGANCg0KXA0KDQpUaGUgZm9sbG93aW5nIGZpZ3VyZSBpbGx1c3RyYXRlcyB0aGUgR2F1c3NpYW4ga2VybmVsIGRlbnNpdHkgZXN0aW1hdGlvbiBmb3IgZGF0YSBnZW5lcmF0ZWQgZnJvbSBhICoqdHdvLWNvbXBvbmVudCBHYXVzc2lhbiBtaXh0dXJlIG1vZGVsIChkaXN0cmlidXRpb24pKiogd2l0aCBkaWZmZXJlbnQgYmFuZHdpZHRocy4NCg0KKiBCdWlsdC1pbiBLREU6IGggPSAwLjQNCiogTWFudWFsIEtERTogaCA9IDAuMzUNCg0KYGBge3J9DQojIENvbXBsZXRlIEtERSB2aXN1YWxpemF0aW9uDQpzZXQuc2VlZCgxMjMpDQojIyBtaXh0dXJlIGRlbnNpdHkgd2l0aCBtaXhpbmcgcHJvcG9ydGlvbiBhbHBoYSA9IDAuNSAoaS5lLiwgNTAlIHRvIDUwJSkNCmRhdDEgPC0gcm5vcm0oMzAsIG1lYW4gPSAtMSwgc2QgPSAwLjcpDQpkYXQyIDwtIHJub3JtKDMwLCBtZWFuID0gMiwgc2QgPSAwLjUpDQpzYW1wbGUuZGF0YSA8LSBjKGRhdDEsIGRhdDIpDQojIw0KIyMgdHJ1ZSBtaXh0dXJlIGRlbnNpdHkNCngudHJ1ZT0gc2VxKC0zLjUsIDQsbGVuZ3RoID0gMjAwKQ0KdHJ1ZS5kZW4gPC0gMC41KmRub3JtKHgudHJ1ZSwgbWVhbiA9IC0xLCBzZCA9IDAuNykgKyAwLjUqZG5vcm0oeC50cnVlLCBtZWFuID0gMiwgc2QgPSAwLjUpDQoNCiMgS0RFIGJhc2VkIG9uIGJ1aWx0LWluIGZ1bmN0aW9uIGRlbnNpdHkoKTogYmFuZHdpZHRoID0gMC40DQprZGUgPC0gZGVuc2l0eShzYW1wbGUuZGF0YSwgYncgPSAwLjM1KQ0Ka2RlLnkgPC1hcHByb3goa2RlJHgsIGtkZSR5LCB4b3V0ID0geC50cnVlKSR5DQoNCiMjIE1hbnVhbCBjYWxjdWxhdGlvbiB3aXRoIGggPSAwLjM1DQpteS5rZGUgPC0gbXkua2VyZihpbi5kYXRhID0gc2FtcGxlLmRhdGEsIGggPSAwLjM1LCBvdXQueCA9IHgudHJ1ZSkNCg0KIyBDcmVhdGUgaW5kaXZpZHVhbCBrZXJuZWxzIHBsb3Q6IHRoZSBiYW5kd2lkdGggPSBzdGFuZGFyZCBkZXZpYXRpb24NCmluZGl2aWR1YWwua2VybmVscyA8LSBkYXRhLmZyYW1lKCkNCmZvciAoaSBpbiAxOmxlbmd0aChzYW1wbGUuZGF0YSkpIHsNCiAgbiA8LSBsZW5ndGgoc2FtcGxlLmRhdGEpDQogIGtlcm5lbC55IDwtIGRub3JtKHgudHJ1ZSwgbWVhbiA9IHNhbXBsZS5kYXRhW2ldLCBzZCA9IDAuMzUpIC8gbg0KICBpbmRpdmlkdWFsLmtlcm5lbHMgPC0gcmJpbmQoaW5kaXZpZHVhbC5rZXJuZWxzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKHggPSB4LnRydWUsIHkgPSBrZXJuZWwueSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb2ludCA9IGFzLmZhY3RvcihpKSkpDQp9DQoNCiMgQ3JlYXRlIGNvbXBhcmlzb24gZGF0YSBmcmFtZQ0Ka2RlLmNvbXBhcmlzb24gPC0gZGF0YS5mcmFtZSgNCiAgeCA9IHgudHJ1ZSwNCiAgdHJ1ZS5kZW4gPSB0cnVlLmRlbiwNCiAgYnVpbHRpbi5kZW4gPSBrZGUueSwgDQogIG15LmRlbiA9IG15LmtkZSANCikNCg0KIyMgTWFrZSBsb25nIHRhYmxlDQprZGUuY29tcGFyaXNvbi5sb25nIDwtIGtkZS5jb21wYXJpc29uICU+JQ0KICBwaXZvdF9sb25nZXIoY29scyA9IGModHJ1ZS5kZW4sIGJ1aWx0aW4uZGVuLCBteS5kZW4pLCANCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIkRlbi5UeXBlIiwgdmFsdWVzX3RvID0gIkRlbnNpdHkiKQ0KDQojIGdncGxvdCBLREUgd2l0aCBkaWZmZXJlbnQgYmFuZHdpZHRocw0Ka2RlLnR5cGUuZWZmZWN0LnBsdCA8LSBnZ3Bsb3Qoa2RlLmNvbXBhcmlzb24ubG9uZywgIGFlcyh4ID0geCwgeSA9IERlbnNpdHksIGNvbG9yID0gRGVuLlR5cGUpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAxKSArDQogIGdlb21fbGluZShkYXRhID0gaW5kaXZpZHVhbC5rZXJuZWxzLCBhZXMoeCA9IHgsIHkgPSB5LCBncm91cCA9IHBvaW50KSwgDQogICAgICAgICAgICBjb2xvciA9ICJzdGVlbGJsdWUiLCBhbHBoYSA9IDAuNSwgbGluZXdpZHRoID0gMC4zKSArDQogIGdlb21fcnVnKGRhdGEgPSBkYXRhLmZyYW1lKHggPSBzYW1wbGUuZGF0YSksIGFlcyh4ID0geCwgeSA9IDApLCANCiAgICAgICAgICAgaW5oZXJpdC5hZXMgPSBGQUxTRSwgYWxwaGEgPSAwLjMpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKA0KICAgIHZhbHVlcyA9IGMoInRydWUuZGVuIiA9ICJibHVlIiwgImJ1aWx0aW4uZGVuIiA9ICJyZWQiLCAibXkuZGVuIiA9ICJvcmFuZ2UiKSwNCiAgKSArDQogIGxhYnModGl0bGUgPSAiS2VybmVsIERlbnNpdHkgRXN0aW1hdGlvbiBDb21wYXJpc29uOiBCdWlsdC1pbiB2cyBNYW51YWwgIiwNCiAgICAgICB4ID0gIngiLCB5ID0gIkRlbnNpdHkiKSArDQogICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwNCiAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4odCA9IDM1LCByID0gMjAsIGIgPSAzMCwgbCA9IDMwLCB1bml0ID0gInB0IikpDQpnZ3Bsb3RseShrZGUudHlwZS5lZmZlY3QucGx0ICkNCg0KYGBgDQoNCg0KKipDb21tb24gS2VybmVsIEZ1bmN0aW9ucyoqOg0KDQpHYXVzc2lhbjogJEsodSkgPSBcZnJhY3sxfXtcc3FydHsyXHBpfX0gZV57LVxmcmFjezF9ezJ9dV4yfSQNCg0KRXBhbmVjaG5pa292OiAkSyh1KSA9IFxmcmFjezN9ezR9KDEgLSB1XjIpJCBmb3IgJHx1fCBcbGVxIDEkDQoNClJlY3Rhbmd1bGFyOiAkSyh1KSA9IFxmcmFjezF9ezJ9JCBmb3IgJHx1fCBcbGVxIDEkDQoNCg0KDQoNCiMjIEJhbmR3aWR0aCBTZWxlY3Rpb24NCg0KDQoqKkJhbmR3aWR0aCBTZWxlY3Rpb24qKjogVGhlIGJhbmR3aWR0aCAkaCQgY29udHJvbHMgdGhlIHNtb290aG5lc3Mgb2YgdGhlIGVzdGltYXRlOg0KDQoqIFNtYWxsICRoJDogdW5kZXItc21vb3RoaW5nIChoaWdoIHZhcmlhbmNlKQ0KDQoqIExhcmdlICRoJDogb3Zlci1zbW9vdGhpbmcgKGhpZ2ggYmlhcykNCg0KVGhlIGJhbmR3aWR0aCAkaCQgaXMgYSBoeXBlcnBhcmFtZXRlciB0aGF0IGlzIGNob3NlbiBieSB1c2Vycy4gVGhlcmUgYXJlICoqZGlmZmVyZW50KiogYWxnb3JpdGhtcyBmb3IgZXN0aW1hdGluZyBpdCBiYXNlZCBvbiBkaWZmZXJlbnQgY3JpdGVyaWEuIEhvd2V2ZXIsIHRoZXJlIGFyZSAqKmJhc2VsaW5lIGZvcm11bGFzKiogdG8gc2VsZWN0IGFuIGluaXRpYWwgYmFuZHdpZHRoLiBUaGUgZm9sbG93aW5nIGlzIGxpc3Qgb2YgZm9ybXVsYXMgZm9yIGRpZmZlcmVudCBrZXJuZWxzLg0KDQpEZW5vdGUgYHNkID0gc2FtcGxlIHN0YW5kYXJkIGRldmlhdGlvbmAsIGBJUVIgPSBpbnRlcnF1YXJ0aWxlIHJhbmdlYCwgYW5kIGBuID0gc2FtcGxlIHNpemVgLiBXZSBjYW4gY2FsY3VsYXRlIHRoZXNlIHN0YXRpc3RpY3MgZnJvbSB0aGUgZ2l2ZW4gZGF0YS4NCg0KDQoqIFRoZSBvcHRpbWFsIGluaXRpYWwgYmFuZHdpZHRoIHNlbGVjdGlvbiBmb3IgYSAqKkdhdXNzaWFuIGtlcm5lbCoqLCB1bmRlciB0aGUgYXNzdW1wdGlvbiBvZiBub3JtYWxseSBkaXN0cmlidXRlZCBkYXRhLCBpcyB0aGUgKipOb3JtYWwgUmVmZXJlbmNlIERpc3RyaWJ1dGlvbiwgcnVsZS1vZi10aHVtYiAwIChucmQwKSoqLiBUaGlzIHNlcnZlcyBhcyB0aGUgZGVmYXVsdCBiYW5kd2lkdGggaW4gUidzIGBkZW5zaXR5KClgIGZ1bmN0aW9uLg0KDQokJA0KXHRleHR7Ynd9ID0gMC45IFx0aW1lcyBcbWluKFx0ZXh0e3NkfSwgXHRleHR7SVFSfS8xLjM0KSBcdGltZXMgbl57LTEvNX0NCiQkDQoNCg0KDQoqIEZvciB0aGUgKipFcGFuZWNobmlrb3Yga2VybmVsKiosIHRoZSBpbml0aWFsIHN1Z2dlc3RlZCBjaG9pY2UgaXMNCg0KJCQNClx0ZXh0e2J3fSA9IDIuMzQgXHRpbWVzIFx0ZXh0e3NkfSAgXHRpbWVzIG5eey0xLzV9DQokJA0KDQoqIEZvciAqKnJlY3Rhbmd1bGFyIGtlcm5lbCoqLCB0aGUgaW5pdGlhbCBzdWdnZXN0ZWQgY2hvaWNlIGlzIA0KDQokJA0KXHRleHR7Ynd9ID0gMS44NCBcdGltZXMgXHRleHR7c2R9ICBcdGltZXMgbl57LTEvNX0NCiQkDQpcDQoNCg0KKipFeGFtcGxlKio6IEVmZmVjdCBvZiBiaW53aWR0aCBpbiBLZXJuZWwgRGVuc2l0eSBFc3RpbWF0aW9uLiBXZSBzdGlsbCB1c2UgdGhlIDItY29tcG9uZW50IEdhdXNzaWFuIG1peHR1cmUgYXMgYW4gZXhhbXBsZS4gVGhlIHRocmVlIGJhbmR3aWR0aHMgdXNlZCBpbiB0aGUgZXhhbXBsZSBhcmUgJGggPSAwLjUkIChkZWZhdWx0KSwgMC4yIGFuZCAxLg0KDQpgYGB7cn0NCiMgR2VuZXJhdGUgc2FtcGxlIGRhdGENCnNldC5zZWVkKDEyMykNCnNhbXBsZS5kYXRhIDwtIGMocm5vcm0oMjAwLCBtZWFuID0gLTEsIHNkID0gMC44KSwgDQogICAgICAgICAgICAgICAgIHJub3JtKDIwMCwgbWVhbiA9IDIsIHNkID0gMSkpDQoNCiMgQ3JlYXRlIEtERSB3aXRoIGRpZmZlcmVudCBiYW5kd2lkdGhzDQprZGUuZGVmYXVsdCA8LSBkZW5zaXR5KHNhbXBsZS5kYXRhLCBrZXJuZWwgPSAiZ2F1c3NpYW4iKQ0Ka2RlLnNtYWxsLmJ3IDwtIGRlbnNpdHkoc2FtcGxlLmRhdGEsIGJ3ID0gMC4yLCBrZXJuZWwgPSAiZ2F1c3NpYW4iKQ0Ka2RlLmxhcmdlLmJ3IDwtIGRlbnNpdHkoc2FtcGxlLmRhdGEsIGJ3ID0gMSwga2VybmVsID0gImdhdXNzaWFuIikNCg0KIyBDcmVhdGUgY29tcGFyaXNvbiBkYXRhIGZyYW1lDQprZGUuY29tcGFyaXNvbiA8LSBkYXRhLmZyYW1lKA0KICB4ID0ga2RlLmRlZmF1bHQkeCwNCiAgRGVmYXVsdCA9IGtkZS5kZWZhdWx0JHksDQogIFNtYWxsLkJXID0ga2RlLnNtYWxsLmJ3JHksDQogIExhcmdlLkJXID0ga2RlLmxhcmdlLmJ3JHkNCikNCg0Ka2RlLmNvbXBhcmlzb24ubG9uZyA8LSBrZGUuY29tcGFyaXNvbiAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKERlZmF1bHQsIFNtYWxsLkJXLCBMYXJnZS5CVyksIA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiQmFuZHdpZHRoIiwgdmFsdWVzX3RvID0gIkRlbnNpdHkiKQ0KDQojIFBsb3QgS0RFIHdpdGggZGlmZmVyZW50IGJhbmR3aWR0aHMNCmJpbi5lZmZlY3QucGx0IDwtIGdncGxvdChrZGUuY29tcGFyaXNvbi5sb25nLCBhZXMoeCA9IHgsIHkgPSBEZW5zaXR5LCBjb2xvciA9IEJhbmR3aWR0aCkpICsNCiAgZ2VvbV9saW5lKGxpbmV3aWR0aCA9IDEpICsNCiAgZ2VvbV9ydWcoZGF0YSA9IGRhdGEuZnJhbWUoeCA9IHNhbXBsZS5kYXRhKSwgYWVzKHggPSB4LCB5ID0gMCksIA0KICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFLCBhbHBoYSA9IDAuMykgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwoDQogICAgdmFsdWVzID0gYygiRGVmYXVsdCIgPSAiYmx1ZSIsICJTbWFsbC5CVyIgPSAicmVkIiwgIkxhcmdlLkJXIiA9ICJvcmFuZ2UiKSMsDQogICAgI2xhYmVscyA9IGMoIkRlZmF1bHQgKGJ3ID0gMC41KSIsICJTbWFsbCAoYncgPSAwLjIpIiwgIkxhcmdlIChidyA9IDEuMCkiKQ0KICApICsNCiAgbGFicyh0aXRsZSA9ICJLZXJuZWwgRGVuc2l0eSBFc3RpbWF0aW9uIHdpdGggRGlmZmVyZW50IEJhbmR3aWR0aHMiLA0KICAgICAgICNzdWJ0aXRsZSA9ICJTYW1wbGUgZnJvbSBtaXh0dXJlIG9mIHR3byBub3JtYWwgZGlzdHJpYnV0aW9ucyIsDQogICAgICAgeCA9ICJ4IiwgeSA9ICJEZW5zaXR5IikgKw0KICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAzNSwgciA9IDIwLCBiID0gMzAsIGwgPSAzMCwgdW5pdCA9ICJwdCIpKQ0KZ2dwbG90bHkoYmluLmVmZmVjdC5wbHQgKQ0KYGBgDQoNCg0KKipDb21wYXJpc29uIG9mIERpZmZlcmVudCBLZXJuZWxzKio6IFRoZSBuZXh0IGZpZ3VyZSBzaG93cyB0aGUgZGlmZmVyZW5jZSBhbW9uZyB0aGUgYWJvdmUgbWVudGlvbmVkIHRocmVlIGtlcm5lbCBmdW5jdGlvbnMuDQoNCmBgYHtyfQ0KIyBDb21wYXJlIGRpZmZlcmVudCBrZXJuZWwgZnVuY3Rpb25zDQp4Lmtlcm5lbCA8LSBzZXEoLTMsIDMsIGxlbmd0aC5vdXQgPSAxMDAwKQ0KDQojIERlZmluZSBrZXJuZWwgZnVuY3Rpb25zDQpnYXVzc2lhbi5rZXJuZWwgPC0gZG5vcm0oeC5rZXJuZWwpDQplcGFuZWNobmlrb3Yua2VybmVsIDwtIGlmZWxzZShhYnMoeC5rZXJuZWwpIDw9IDEsIDAuNzUgKiAoMSAtIHgua2VybmVsXjIpLCAwKQ0KcmVjdGFuZ3VsYXIua2VybmVsIDwtIGlmZWxzZShhYnMoeC5rZXJuZWwpIDw9IDEsIDAuNSwgMCkNCg0Ka2VybmVscy5kZiA8LSBkYXRhLmZyYW1lKA0KICB4ID0geC5rZXJuZWwsDQogIEdhdXNzaWFuID0gZ2F1c3NpYW4ua2VybmVsLA0KICBFcGFuZWNobmlrb3YgPSBlcGFuZWNobmlrb3Yua2VybmVsLA0KICBSZWN0YW5ndWxhciA9IHJlY3Rhbmd1bGFyLmtlcm5lbA0KKQ0KDQprZXJuZWxzLmxvbmcgPC0ga2VybmVscy5kZiAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKEdhdXNzaWFuLCBFcGFuZWNobmlrb3YsIFJlY3Rhbmd1bGFyKSwgDQogICAgICAgICAgICAgICBuYW1lc190byA9ICJLZXJuZWwiLCB2YWx1ZXNfdG8gPSAiRGVuc2l0eSIpDQoNCmRpZi5rZXJuLnBsdCAgPC0gZ2dwbG90KGtlcm5lbHMubG9uZywgYWVzKHggPSB4LCB5ID0gRGVuc2l0eSwgY29sb3IgPSBLZXJuZWwpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAxKSArDQogIGxhYnModGl0bGUgPSAiQ29tbW9uIEtlcm5lbCBGdW5jdGlvbnMiLA0KICAgICAgIHggPSAidSIsIHkgPSAiSyh1KSIpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksDQogICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAzNSwgciA9IDIwLCBiID0gMzAsIGwgPSAzMCwgdW5pdCA9ICJwdCIpKQ0KZ2dwbG90bHkoZGlmLmtlcm4ucGx0ICkNCmBgYA0KDQoNCiMgUHJhY3RpY2FsIEltcGxlbWVudGF0aW9uIGluIFINCg0KIyMgRGlzdHJpYnV0aW9uIG9mIENvbW11dGUgVGltZXMNCg0KQ29uc2lkZXIgY29tbXV0ZSB0aW1lcyAoaW4gbWludXRlKSBvZiBhIGNvbXBhbnkgbG9jYXRlZCBpbiBhIGRvd250b3duIG9mIGEgY2l0eToNCg0KYGBgDQoxMiwgMTUsIDE3LCAxOCwgMTksIDIwLCAyMCwgMjEsIDIyLCAyMywgMjQsIDI1LCAyNiwgMjcsIDU1LCA1OCwgNjAsIDYyLCA2MywgNjUsIA0KNjUsIDY2LCA2NywgNjgsIDcwLCA3MiwgNzMsIDc1LCAyNSwgMTksIDYwLCA2NA0KYGBgDQoNCldlIGFyZSBpbnRlcmVzdGVkIGluIGVzdGltYXRpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiBjb21tdXRlIHRpbWVzIHVzaW5nIGFuIGVtcGlyaWNhbCBkaXN0cmlidXRpb24gZnVuY3Rpb24gYW5kIGEgR2F1c3NpYW4ga2VybmVsIGRlbnNpdHkgZXN0aW1hdG9yLg0KDQoqKlNvbHV0aW9uKiogV2UgZmlyc3QgZXN0aW1hdGUgdGhlIGVtcGlyaWNhbCBjdW11bGF0aXZlIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiAkXGhhdHtGfV9uKHQpJCB3aXRoIGV4cGxpY2l0IGV4cHJlc3Npb24NCg0KJCQNClxoYXR7Rn1fbih0KSA9IFxmcmFje1x0ZXh0e051bWJlciBvZiB9IFRfaSBcbGUgdH17bn0uDQokJA0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTR9DQp0aW1lcyA8LSBjKDEyLCAxNSwgMTcsIDE4LCAxOSwgMjAsIDIwLCAyMSwgMjIsIDIzLCAyNCwgMjUsIDI2LCAyNywgNTUsIDU4LCA2MCwgNjIsIDYzLCA2NSwgDQo2NSwgNjYsIDY3LCA2OCwgNzAsIDcyLCA3MywgNzUsIDI1LCAxOSwgNjAsIDY0KQ0KdW5pcS50aW1lIDwtIHNvcnQodW5pcXVlKHRpbWVzKSkgICAjIG5lZWQgdG8gc29ydCB0aGUgZGF0YSB2YWx1ZXMNCiMjIFIgZnVuY3Rpb24NCm15LkVDREYgPC0gZnVuY3Rpb24oaW5kYXQsIG91dHgpew0KICAjIG91dHggLSBhIHZlY3RvciBvZiBnaXZlbiB2YWx1ZXMNCiAgZnJlcS50YWJsZSA8LSB0YWJsZShpbmRhdCkgICAgICAgICAgICAgICAgICAgICAgICAgICMgZnJlcXVlbmN5IHRhYmxlDQogIHVuaXEgPC0gYXMubnVtZXJpYyhuYW1lcyhmcmVxLnRhYmxlKSkgICAgICAgICAgIyB1bmlxdWUgZGF0YSB2YWx1ZXMNCiAgcmVwLnRpbWUgPC0gYXMudmVjdG9yKGZyZXEudGFibGUpICAgICAgICAgICAgICAgICAgICMgZnJlcXVlbmNpZXMgb2YgdGhlIHVuaXF1ZSBkYXRhIHZhbHVlcw0KICBjdW0ucmVsLmZlcSA8LSBjdW1zdW0ocmVwLnRpbWUpL3N1bShyZXAudGltZSkgICAgICAgIyBjdW11bGF0aXZlIHJlbGF0aXZlIGZyZXF1ZW5jaWVzOiBDREYNCiAgY3VtLnByb2IgPC0gTlVMTA0KICBmb3IgKGkgaW4gMTpsZW5ndGgob3V0eCkpew0KICAgIGludHZsLmlkIDwtIHdoaWNoKHVuaXEgPD0gb3V0eFtpXSkgICAgICAjIGlkZW50aWZ5IHRoZSBpbmRleCBtZWV0aW5nIHRoZSBjb25kaXRpb24NCiAgICBjdW0ucHJvYltpXSA8LSBjdW0ucmVsLmZlcVttYXgoaW50dmwuaWQpXSAjIGV4dHJhY3QgdGhlIGN1bXVsYXRpdmUgcHJvYiBhY2NvcmRpbmcgdG8gQ0RGIA0KICB9DQogIGN1bS5wcm9iICAgICAgICAgICAgIA0KfQ0KIyMNCnBsb3QodW5pcS50aW1lLCBteS5FQ0RGKGluZGF0ID0gdGltZXMsIG91dHggPSB1bmlxLnRpbWUpLCB0eXBlID0gInMiLA0KICAgICBtYWluID0gIkVDREYgb2YgQ29tbXV0aW5nIFRpbWVzIiwNCiAgICAgeGxhYiA9ICJDb21tdXRpbmcgVGltZXMiLA0KICAgICB5bGFiID0gIkN1bXVsYXRpdmUgUHJvYmFiaWxpdHkiKQ0KYGBgDQoNCk5leHQsIHdlIGVzdGltYXRlIHRoZSBwcm9iYWJpbGl0eSBkZW5zaXR5IGZ1bmN0aW9uLiBIaXN0b2dyYW1zIGFyZSBjb21tb25seSB1c2VkIHRvIHZpc3VhbGl6ZSB0aGUgc2hhcGUgb2YgdGhlIGRpc3RyaWJ1dGlvbi4NCg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcid9DQpoaXN0KHRpbWVzLCBicmVha3MgPSAxOCwgZnJlcSA9IEZBTFNFKQ0KYGBgDQoNClRoZSBkaXN0cmlidXRpb24gb2YgY29tbXV0ZSB0aW1lcyBpcyBjbGVhcmx5IGJpbW9kYWwuIEluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbiwgd2Ugd2lsbCBpbXBsZW1lbnQgdGhlIEdhdXNzaWFuIGtlcm5lbCBkZW5zaXR5IGVzdGltYXRvciBpbiBSLiANCg0KKipQc2V1ZG8tY29kZSoqOg0KDQpJbnB1dDogICBpbi5kYXQgPSBpbnB1dCBkYXRhIChmaXggZGF0YSBzZXQpDQogICAgICAgICAgICAgIGggPSBiYW5kd2lkdGgNCiAgICAgICAgICBvdXQueCA9IGlucHV0IHggZm9yIGV2YWx1YXRpbmcgdGhlIGRlbnNpdHkgKGNvdWxkIGJlIGEgdmVjdG9yKQ0KT3V0cHV0OiBkZW5zaXR5IHZhbHVlcyBldmFsdWF0ZWQgYXQgeA0KDQoNCmBgYHtyfQ0KIyBpbi5kYXQgPSB0aW1lcw0KIyBoID0gMTINCiMgeCA9IGMoMjAsNTApDQojIGluLmRhdGEgPSB0aW1lcw0KIyMjDQpteS5rZXJmIDwtIGZ1bmN0aW9uKGluLmRhdGEsIGgsIG91dC54KXsNCiAgbiA8LSBsZW5ndGgoaW4uZGF0YSkgICAgICMgc2FtcGxlIHNpemUNCiAgZGVuIDwtIE5VTEwgICAgICAgICAgICAgICMgZGVuc2l0eSB2ZWN0b3IgdG8gc3RvcmUgb3V0cHV0IHZhbHVlcw0KICBmb3IgKGkgaW4gMTpsZW5ndGgob3V0LngpKXsNCiAgICBkZW5baV0gPC0gc3VtKGRub3JtKG91dC54W2ldLCBtZWFuPWluLmRhdGEsIHNkID0gaCkpL24gICMga2VybmVsIGRlbnNpdHkgZm9ybXVsYQ0KICB9DQogIGRlbiAgIyByZXR1cm4gZGVuc2l0eSB2YWx1ZXMgYmFzZWQgb24gdGhlIG91dC54IHZhbHVlcw0KfQ0KYGBgDQoNCg0KKipSIEJ1aWx0LWluIEVzdGltYXRvcjogYGRlbnNpdHkoKWAqKg0KDQpCYXNlIFIgaGFzIGEgYnVpbHQtaW4gZGVuc2l0eSBlc3RpbWF0b3IgYGRlbnNpdHkoKWAgd2l0aCB0aGUgZm9sbG93aW5nIGJhc2ljIHN5bnRheDoNCg0KYGBge30NCmRlbnNpdHkoeCwgYncgPSAibnJkMCIsIGFkanVzdCA9IDEsDQogICAgICAgIGtlcm5lbCA9IGMoImdhdXNzaWFuIiwgImVwYW5lY2huaWtvdiIsICJyZWN0YW5ndWxhciIsDQogICAgICAgICAgICAgICAgICAgInRyaWFuZ3VsYXIiLCAiYml3ZWlnaHQiLA0KICAgICAgICAgICAgICAgICAgICJjb3NpbmUiLCAib3B0Y29zaW5lIiksDQogICAgICAgIHdlaWdodHMgPSBOVUxMLCB3aW5kb3cgPSBrZXJuZWwsIHdpZHRoLA0KICAgICAgICBnaXZlLlJrZXJuID0gRkFMU0UsIHN1YmRlbnNpdHkgPSBGQUxTRSwNCiAgICAgICAgd2FybldidyA9IHZhcih3ZWlnaHRzKSA+IDAsDQogICAgICAgIG4gPSA1MTIsIGZyb20sIHRvLCBjdXQgPSAzLCBleHQgPSA0LA0KICAgICAgICBvbGQuY29vcmRzID0gRkFMU0UsDQogICAgICAgIG5hLnJtID0gRkFMU0UsIC4uLikNCmBgYA0KDQpUaGUgYnVpbHQtaW4gZnVuY3Rpb24gYXBwcm94KCkgaXMgdXNlZCBpbiBjb25qdW5jdGlvbiB3aXRoIGRlbnNpdHkoKSB0byBldmFsdWF0ZSB0aGUga2VybmVsIGRlbnNpdHkgZXN0aW1hdGUgYXQgc3BlY2lmaWMgaW5wdXQgdmFsdWVzLg0KDQpgYGB7fQ0KYXBwcm94ICAgKHgsIHkgPSBOVUxMLCB4b3V0LCBtZXRob2QgPSAibGluZWFyIiwgbiA9IDUwLA0KICAgICAgICAgIHlsZWZ0LCB5cmlnaHQsIHJ1bGUgPSAxLCBmID0gMCwgdGllcyA9IG1lYW4sIG5hLnJtID0gVFJVRSkNCmBgYA0KDQoNCioqQ29tcGFyaW5nIGBteS5rZXJmKClgIHdpdGggYGRlbnNpdHkoKWAqKg0KDQpXZSBleHBlY3QgdGhlIHR3byBmdW5jdGlvbnMgdG8gYmVoYXZlIHNpbWlsYXJseS4gQW55IG1pbm9yIGRpZmZlcmVuY2VzIGFyZSBkdWUgdG8gYXBwcm94aW1hdGlvbiB0aHJvdWdoIGludGVycG9sYXRpb24uDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJ30NCnh4ID0gc2VxKDAsMTAwLCBsZW5ndGg9MjAwKQ0KIyMgIA0KbXkuZGVuIDwtIG15LmtlcmYoaW4uZGF0YSA9IHRpbWVzLCBoID0gMTIsIG91dC54ID0geHgpDQojIyBkZW5zaXR5KCkNCmtkZSA8LSBkZW5zaXR5KHRpbWVzLCBidyA9IDEyLCBrZXJuZWwgPSAiZ2F1c3NpYW4iKQ0KIyMNCmtkZS55IDwtYXBwcm94KGtkZSR4LCBrZGUkeSwgeG91dCA9IHh4KSR5DQojIyBiYXNlIFIgcGxvdA0KcGxvdCh4eCwgbXkuZGVuLCB0eXBlID0gImwiLCBtYWluID0gIkNvbXBhcmluZyBLZXJuZWwgRGVuc2l0eSBFc3RpbWF0b3JzIiwNCiAgICAgeGxhYiA9ICJDb21tdXRpbmcgVGltZXMiLA0KICAgICB5bGFiID0gIktlcm5lbCBEZW5zaXR5IiwNCiAgICAgbHR5ID0gMSwNCiAgICAgbHdkID0gMiwNCiAgICAgY29sID0gIm5hdnkiKQ0KIyMgYWRkIGRlbnNpdHkgY3VydmUgYmFzZWQgb24gdGhlIGJ1aWx0LWluIGRlbnNpdHkoKQ0KbGluZXMoeHgsIGtkZS55LCBsdHkgPSAyLCBsd2QgPSAyLCBjb2wgPSAib3JhbmdlIikNCmxlZ2VuZCgidG9wcmlnaHQiLCBjKCJteS5rZXJmKCkiLCAiZGVuc2l0eSgpIiksIGx0eSA9YygxLDIpLCBsd2QgPSByZXAoMiwyKSwNCiAgICAgICBjb2wgPSBjKCJuYXZ5IiwgIm9yYW5nZSIpLCBjZXggPSAwLjgsIGJ0eSA9ICJuIikNCmBgYA0KDQoNCiMjIFNpbXVsYXRlZCBEYXRhIC0gVHdvLWNvbXBvbmVudCBOb3JtYWwgTWl4dHVyZSBEaXN0cmlidXRpb24NCg0KKipDb21wbGV0ZSBFeGFtcGxlKio6IERpc3RyaWJ1dGlvbiBBbmFseXNpcw0KDQpgYGB7cn0NCiMgR2VuZXJhdGUgc2FtcGxlIGRhdGEgZnJvbSBhIG1peHR1cmUgZGlzdHJpYnV0aW9uDQpzZXQuc2VlZCgxMjMpDQpuIDwtIDEwMDANCmRhdGEubWl4dHVyZSA8LSBjKHJub3JtKG4qMC42LCBtZWFuID0gMCwgc2QgPSAxKSwgDQogICAgICAgICAgICAgICAgICBybm9ybShuKjAuNCwgbWVhbiA9IDMsIHNkID0gMC41KSkNCg0KIyBDYWxjdWxhdGUgZW1waXJpY2FsIENERg0KZW1wLmNkZiA8LSBlY2RmKGRhdGEubWl4dHVyZSkNCg0KIyBLZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uDQprZGUubWl4dHVyZSA8LSBkZW5zaXR5KGRhdGEubWl4dHVyZSkNCg0KIyBUcnVlIFBERiAoZm9yIGNvbXBhcmlzb24gLSBrbm93biBpbiB0aGlzIHNpbXVsYXRpb24pDQp0cnVlLnBkZiA8LSBmdW5jdGlvbih4KSB7DQogIDAuNiAqIGRub3JtKHgsIG1lYW4gPSAwLCBzZCA9IDEpICsgMC40ICogZG5vcm0oeCwgbWVhbiA9IDMsIHNkID0gMC41KQ0KfQ0KDQojIENyZWF0ZSBjb21wcmVoZW5zaXZlIHBsb3QNCngucGxvdCA8LSBzZXEoLTMsIDYsIGxlbmd0aC5vdXQgPSAxMDAwKQ0KDQpwbG90LmRhdGEgPC0gZGF0YS5mcmFtZSgNCiAgeCA9IHgucGxvdCwNCiAgVHJ1ZS5QREYgPSB0cnVlLnBkZih4LnBsb3QpLA0KICBLREUgPSBhcHByb3goa2RlLm1peHR1cmUkeCwga2RlLm1peHR1cmUkeSwgeG91dCA9IHgucGxvdCkkeSwNCiAgRW1waXJpY2FsLkNERiA9IGVtcC5jZGYoeC5wbG90KSwNCiAgVHJ1ZS5DREYgPSAwLjYgKiBwbm9ybSh4LnBsb3QsIG1lYW4gPSAwLCBzZCA9IDEpICsgDQogICAgICAgICAgICAgMC40ICogcG5vcm0oeC5wbG90LCBtZWFuID0gMywgc2QgPSAwLjUpDQopDQoNCiMgUERGIGNvbXBhcmlzb24NCnBkZi5wbG90IDwtIGdncGxvdChwbG90LmRhdGEsIGFlcyh4ID0geCkpICsNCiAgZ2VvbV9saW5lKGFlcyh5ID0gVHJ1ZS5QREYsIGNvbG9yID0gIlRydWUgUERGIiksIGxpbmV3aWR0aCA9IDEpICsNCiAgZ2VvbV9saW5lKGFlcyh5ID0gS0RFLCBjb2xvciA9ICJLREUiKSwgbGluZXdpZHRoID0gMSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBnZW9tX2hpc3RvZ3JhbShkYXRhID0gZGF0YS5mcmFtZSh4ID0gZGF0YS5taXh0dXJlKSwgDQogICAgICAgICAgICAgICAgIGFlcyh4ID0geCwgeSA9IC4uZGVuc2l0eS4uKSwgDQogICAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JheSIsIGFscGhhID0gMC41LCBiaW5zID0gMzApICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIlRydWUgUERGIiA9ICJnb2xkIiwgIktERSIgPSAicHVycGxlIikpICsNCiAgbGFicyh0aXRsZSA9ICIiLA0KICAgICAgIHggPSAieCIsIHkgPSAiRGVuc2l0eSIsIGNvbG9yID0gIiIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgQ0RGIGNvbXBhcmlzb24NCmNkZi5wbG90IDwtIGdncGxvdChwbG90LmRhdGEsIGFlcyh4ID0geCkpICsNCiAgZ2VvbV9saW5lKGFlcyh5ID0gVHJ1ZS5DREYsIGNvbG9yID0gIlRydWUgQ0RGIiksIGxpbmV3aWR0aCA9IDEpICsNCiAgZ2VvbV9saW5lKGFlcyh5ID0gRW1waXJpY2FsLkNERiwgY29sb3IgPSAiRW1waXJpY2FsIENERiIpLCANCiAgICAgICAgICAgIGxpbmV3aWR0aCA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIlRydWUgQ0RGIiA9ICJyZWQiLCAiRW1waXJpY2FsIENERiIgPSAiYmx1ZSIpKSArDQogIGxhYnModGl0bGUgPSAiIiwNCiAgICAgICB4ID0gIngiLCB5ID0gIkNERiIsIGNvbG9yID0gIiIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgRGlzcGxheSBwbG90cyBzaWRlIGJ5IHNpZGUNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0Kc3VicGxvdChnZ3Bsb3RseShwZGYucGxvdCksIGdncGxvdGx5KGNkZi5wbG90KSkNCmBgYA0KDQoNCg0KIyBTdW1tYXJ5IA0KDQpUaGUgZm9sbG93aW5nIHRhYmxlIHN1bW1hcml6ZXMgZGlzdHJpYnV0aW9ucyBmdW5jdGlvbnMgYW5kIHRoZWlyIGVzdGltYXRvcnMgYW5kIHByb3BlcnRpZXMuDQoNCmBgYHtyfQ0Kc3VtbWFyeV90YWJsZSA8LSBkYXRhLmZyYW1lKA0KICBGdW5jdGlvbiA9IGMoIkNERiIsICJQREYiLCAiU3Vydml2YWwiLCAiSGF6YXJkIiwgIkVtcGlyaWNhbCBDREYiLCAiS0RFIiksDQogIERlZmluaXRpb24gPSBjKA0KICAgICIkRih4KSA9IFAoWCBcXGxlcSB4KSQiLA0KICAgICIkZih4KSA9IFxcZnJhY3tkfXtkeH1GKHgpJCIsDQogICAgIiRTKHgpID0gUChYID4geCkgPSAxIC0gRih4KSQiLA0KICAgICIkXFxsYW1iZGEoeCkgPSBcXGZyYWN7Zih4KX17Uyh4KX0kIiwNCiAgICAiJFxcaGF0e0Z9X24oeCkgPSBcXGZyYWN7MX17bn1cXHN1bSBJKFhfaSBcXGxlcSB4KSQiLA0KICAgICIkXFxoYXR7Zn1faCh4KSA9IFxcZnJhY3sxfXtuaH1cXHN1bSBLKFxcZnJhY3t4LVhfaX17aH0pJCINCiAgKSwNCiAgUHJvcGVydGllcyA9IGMoDQogICAgIk5vbi1kZWNyZWFzaW5nLCByaWdodC1jb250aW51b3VzIiwNCiAgICAiTm9uLW5lZ2F0aXZlLCBpbnRlZ3JhdGVzIHRvIDEiLA0KICAgICJOb24taW5jcmVhc2luZywgcmlnaHQtY29udGludW91cyIsDQogICAgIkluc3RhbnRhbmVvdXMgZmFpbHVyZSByYXRlIiwNCiAgICAiU3RlcCBmdW5jdGlvbiwgdW5iaWFzZWQiLA0KICAgICJTbW9vdGgsIGRlcGVuZHMgb24gYmFuZHdpZHRoIg0KICApDQopDQoNCmthYmxlKHN1bW1hcnlfdGFibGUsIGNhcHRpb24gPSAiU3VtbWFyeSBvZiBEaXN0cmlidXRpb24gRnVuY3Rpb25zIGFuZCBFc3RpbWF0b3JzIikNCmBgYA0KDQoNCioqS2V5IFBvaW50cyoqDQoNCiogQ0RGIHByb3ZpZGVzIGNvbXBsZXRlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBkaXN0cmlidXRpb24gb2YgYSByYW5kb20gdmFyaWFibGUNCg0KKiBQREYgZGVzY3JpYmVzIHRoZSByZWxhdGl2ZSBsaWtlbGlob29kIG9mIGEgY29udGludW91cyByYW5kb20gdmFyaWFibGUNCg0KKiBTdXJ2aXZhbCBhbmQgSGF6YXJkIGZ1bmN0aW9ucyBhcmUgZXNzZW50aWFsIGZvciB0aW1lLXRvLWV2ZW50IGRhdGEgYW5hbHlzaXMNCg0KKiBFbXBpcmljYWwgQ0RGIGlzIGEgbm9uLXBhcmFtZXRyaWMgZXN0aW1hdG9yIHRoYXQgY29udmVyZ2VzIHRvIHRoZSB0cnVlIENERg0KDQoqIEtlcm5lbCBEZW5zaXR5IEVzdGltYXRpb24gcHJvdmlkZXMgc21vb3RoIFBERiBlc3RpbWF0ZXMgYnV0IHJlcXVpcmVzIGNhcmVmdWwgYmFuZHdpZHRoIHNlbGVjdGlvbg0KDQoqIFRoZSBjaG9pY2Ugb2Yga2VybmVsIHR5cGljYWxseSBoYXMgbGVzcyBpbXBhY3QgdGhhbiBiYW5kd2lkdGggc2VsZWN0aW9uIGluIEtERQ==